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 oflambda
bind
:f(x)
...g(result of f)
...lambda(x)
... result oflambda
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 typer
. The function(a -> (r -> b))
given to(>>=)
allows you to choose the next function to return given the result from the current value (a functionr -> a
).f r
has typea
andk (f r)
has typer -> b
which is the next function to apply.In your code
g(f(x))
is therefore a function which expects a single argument of typer
. The caller ofbind
can choose this function based on the value returned by the previous function e.g.The function will be given
x
as an input and can choose the next stage in the computation based on the result ofinc(x)
e.g.A few footnotes to Lee's answer:
Because
bind
is backwards. Compare(>>=)
and its flipped version(=<<)
:Or, in your specific example:
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 withfmap
/(<$>)
and(<*>)
are much more obvious:That is an accidental fact about the function instances. Let's put the specialised signatures side by side:
Monad
goes beyondApplicative
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 typer
. 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 theMonad
instance for(->) r
entirely equivalent to theApplicative
one.