I'm trying to improve my understanding of Applicative
s and Monad
s by implementing their function instances in Javascript. My knowledge of Haskell is limited and I hope that my question makes sense at all.
Here are my implementations of fmap
, <*>
and >>=
for the Functor
, Applicative
and Monad
typeclasses in Javascript:
const fmap = f => g => x => f(g(x)); // B combinator
const apply = f => g => x => f(x) (g(x)); // S combinator
const bind = f => g => x => g(f(x)) (x); // ?
I am not sure whether bind
is the correct translation of the Haskell implementation:
(>>=) :: (r -> a) -> (a -> (r -> b)) -> r -> b
instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r
Provided that bind
is correct, how is it interpreted? I know that an Applicative
can sequence effectful computations. I also know that a Monad
in addition allows you to determine a next effect according to the result of a previous one.
I can see the sequences (eager evaluation order in Javascript):
apply
: f(x)
... g(x)
... lambda(result of g)
... result of lambda
bind
: f(x)
... g(result of f)
... lambda(x)
... result of lambda
However, the bind
function looks pretty weird. Why are f
and g
nested the other way around? How is the specific Monad
behavior (determines a next effect according to a previous one) reflected in this implementation? Actually g(f(x)) (x)
looks like a function composition with flipped arguments, where g
is a binary function.
When I apply apply
/bind
with an unary and a binary function, they yield the same result. This doesn't make much sense.
The values in the monad instance for functions have type r -> a
for some fixed type r
. The function (a -> (r -> b))
given to (>>=)
allows you to choose the next function to return given the result from the current value (a function r -> a
). f r
has type a
and k (f r)
has type r -> b
which is the next function to apply.
In your code g(f(x))
is therefore a function which expects a single argument of type r
. The caller of bind
can choose this function based on the value returned by the previous function e.g.
var inc = x => x + 1;
var f = bind(inc)(function(i) {
if(i <= 5) { return x => x * 2; }
else { return x => x * 3; }
});
The function will be given x
as an input and can choose the next stage in the computation based on the result of inc(x)
e.g.
f(2) //4;
f(5) //15;
A few footnotes to Lee's answer:
However, the bind
function looks pretty weird. Why are f
and g
nested the other way around?
Because bind
is backwards. Compare (>>=)
and its flipped version (=<<)
:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
Or, in your specific example:
(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b)
(=<<) :: (a -> (r -> b)) -> (r -> a) -> (r -> b)
While in practice we tend to use (>>=)
more often than (=<<)
(because of how (>>=)
, syntactically speaking, lends itself well to the kind of pipeline monads are often used to build), from a theoretical point of view (=<<)
is the most natural way of writing it. In particular, the parallels and differences with fmap
/(<$>)
and (<*>)
are much more obvious:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(=<<) :: Monad f => (a -> f b) -> f a -> f b
When I apply apply
/bind
with an unary and a binary function, they yield the same result. This doesn't make much sense.
That is an accidental fact about the function instances. Let's put the specialised signatures side by side:
(<*>) :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
(=<<) :: (a -> (r -> b)) -> (r -> a) -> (r -> b)
Monad
goes beyond Applicative
by providing the means to determine the next effect according to previous results (as opposed to "previous effect" -- Applicative
can do that already). The effect, in this case, consists of a function that generates values given an argument of type r
. Now, since functions with multiple arguments (i.e. functions that return functions) can be flipped, it happens that there is no significant difference between (r -> (a -> b))
and (a -> (r -> b))
(flip
can trivially change one into the other), which makes the Monad
instance for (->) r
entirely equivalent to the Applicative
one.