Reputation: 1581
Could anyone please tell me the difference between map/fmap
(defined by Functor) and flatMap/bind/lift
(defined by Monads) in https://gist.github.com/babygau/b80861ff5eabc582f7fa#file-monad-js? The former takes a function and return a Monad, the latter takes a function and return a function which then also returns a Monad. The question is why the latter need take extra step to return a Monad???
// Monads apply a function that returns a wrapped values to a wrapped value
// and then return a wrapped value
// Monads are chainable
class Wrapper {
constructor(val) {
console.log('Created a wrapped value ' + val);
this.val = val;
}
// Functor's `fmap` in Haskell
map(func) {
return new Wrapper(func(this.val));
}
// Monad's `>>=` (pronounced bind) in Haskell
flatMap(func) {
return func(this.val);
}
// Monad's `return` in Haskell
static of(val) {
return new Wrapper(val);
}
}
// Monad
console.log(Wrapper.of([1,2,3]).flatMap(function(val) {val.push(4);return Wrapper.of(val);})); //=> [1, 2, 3, 4]
// Functor
console.log(Wrapper.of([1,2,3]).map(function(val) {val.push(4); return val;})); //=> [1, 2, 3, 4]
Upvotes: 2
Views: 587
Reputation: 1559
There are various reasons: sometimes its not even possible to return a value. Take JS promises for example, and lets say bind
was then
. If you do an async operation within then
s callback you cannot return a value - but you can return a promise.
In most languages with static types its not possible for the callback to then to return both a sync value or a promise. So promises in those languages would have 2 methods: fmap
only lets you apply sync functions to the promise wrapper, bind
only async.
Upvotes: 0
Reputation: 39380
Your example:
// Monad
console.log(Wrapper.of([1,2,3]).flatMap(function(val) {val.push(4);return Wrapper.of(val);})); //=> [1, 2, 3, 4]
// Functor
console.log(Wrapper.of([1,2,3]).map(function(val) {val.push(4); return val;})); //=> [1, 2, 3, 4]
Is overly simplified. If you have a function of type a -> b
, like you do (.push(4) :: Array -> Array
), it's trivial to convert it to a -> m b
(this is return
), or relatively easy to lift it directly to m a -> m b
like your map
does.
However, we're looking at a case where your Wrapper
is an Identity Monad. Consider real Maybe
:
divBy2 :: Int -> Maybe Int
divBy2 a = if a `mod` 2 == 0 then Just a `div` 2 else Nothing
This function expresses a computation in Maybe
very clearly. If you have >>=
, you can chain it easily:
pure 8 >>= divBy2 >>= divBy2
However, if you only have fmap
, there's no way of chaining such a function! (Without using something like join
, which is defined in terms of >>=
anyway).
Specifically, if you have a value 5 :: Int
and a function Int -> Maybe Int
, you can feed it in that function. But once you've done that, and have a value of Maybe Int
, you'd need to extract the value out of the Maybe
, and only then feed it to the next function. This is exactly what >>=
is doing.
Functions returning actual monadic computations are very powerful, and that's where they have advantage over Functors.
Upvotes: 1
Reputation: 131
fmap/map
, usually defined like (a -> b) -> f a -> f b
in Haskell, and usually more like f a -> (a -> b) -> f b
in js, expect the provided function to transform only the value in the monad, and the result monad is created by fmap
.
bind/flatMap/chain
, has a signature of f a -> (a -> f b) -> f b
, has more freedom --- it can return monadic values beyond what Monad.of(singleValue)
can express.
As an example, the List
monad has a fmap
method to return a List
of the same length with values transformed by provided function, so that:
fmap (1 +) [1, 2, 3] == [2, 3, 4]
But what if you want a List
of different length? It is not possible with fmap
because fmap
always return a List
of the same length. bind
solves that.
Prelude> [1..3] >>= \x -> [x..x+2]
[1,2,3,2,3,4,3,4,5]
bind
, or flatMap
, for List
s are usually defined as concatenating the resulting List
s together, so that you can return a List
to say "I want this element to become zero, or 2, or more elements in the resulting List
".
Upvotes: 3
Reputation: 2564
Because it can make for elegant and convenient code. For example, you have multiple operations that can return MyObject
or null
(in a monad, of course, so perhaps Option<MyObject>
or so). You can easily chain them together, without actually checking if they returned null or not. If any operation returns null, all the other operations simply won't do anything.
//I assume Option is a kind of Wrapper
function canReturnNull(myVal) {
//something that can return Option(myChangedVal) or Option(null)
}
var result = Option(myObj).flatMap(canReturnNull1).flatMap(canReturnNull2).flatMap(canReturnNull3);
If it returned a value instead of a monad, I could not chain the operations together like that.
That is, of course, for the Option monad. I recommend reading about the various kinds of monads in Haskell tutorials.
Upvotes: 1