This is a bit mind-blowing, so strap in :-)
A monad is a type constructor that takes one type parameter. For example, Maybe
is such a type constructor. So, Maybe
is a monad, and then Maybe Int
is a monadic value in that monad. Similarly, IO
is a monad, and then IO Int
is a monadic value in that monad.
Now, it is also possible to make a monad out of a type constructor that has two type parameters. To do that, we just need to fix the first one. For example, look at Either
: it has two parameters, but a monad must have only one. So we fix the first parameter. Thus Either Bool
is a monad, and then Either Bool Int
is a monadic value in that monad. Similarly, Either String
is a completely different monad, and then Either String Int
is a monadic value in that monad.
On the other hand, look at how functions are denoted:
a -> b
But this is just infix-operator trickery, similar to 5 + 42
or "foo" <> "bar"
. This infix notation can be "canonicalized" like this:
(->) a b
So really, "function" can be seen as a type constructor that has two type parameters, just like Either
does. For the "function" constructor, these parameters have a specific meaning - one is "function input" and the other is "function output".
Ok, now we're ready to look at functions as monads. Just like with Either
, in order to do this, we have to fix the first type parameter. Thus, for example, (->) Bool
is a monad, and then (->) Bool Int
(also known as Bool -> Int
) is a monadic value in that monad. Similarly, (->) String
is a completely different monad, and then (->) String Int
(also known as String -> Int
) is a monadic value in that monad.
To give you an intuition, one way of looking at it is that a monadic value in such monad means a "promise" - that is, "you give me a String
and I give you an Int
back". And then the standard monadic composition lets you compose such promises together, much like you would compose IO
actions.
With me so far? Ok, good.
Now let's look at how might we implement a bind (aka >>=
). The signature of >>=
is the following:
(>>=) :: m a -> (a -> m b) -> m b
Now let's specialize this to functions. Let's say, for now, that m ~ (->) Bool
. Then we have:
(>>=) :: (->) Bool a -> (a -> (->) Bool b) -> (->) Bool b
Or, if we rewrite the (->)
constructor in infix form, we get:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
So you see - the bind takes a "promise of a
", then a function from a
to a "promise of b
", and then returns a "promise of b
". The implementation, then, is trivial:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
(>>=) promiseOfA f = \theBool -> f (promiseOfA theBool) theBool
here we create a new "promise", which is a function that takes Bool
, and what this "promise" does is pass the given Bool
to the "promise of a
", then passes the resulting a
to the function f
, which returns a "promise of b
", to which we then pass the same Bool
to finally obtain the resulting b
.
Pay attention here: see how theBool
is used twice - first passed to promiseOfA
and then passed again to the result of f
? That's where "reducing of the number of arguments" happens. This is where we pass the argument twice.
But of course, this doesn't have to work just for Bool
s. Any input type is fair game. So we can generalize it like this:
(>>=) :: (input -> a) -> (a -> (input -> b)) -> (input -> b)
(>>=) promiseOfA f = \i -> f (promiseOfA i) i
(compare with the actual definition from the standard library).
Phew!
Ok, now we are finally ready to look at your original example. First, look at reverse
. Since it's a function, we can look at it as a monadic value in the monad (->) [a]
- that is, a "promise of [a]
", where the "input" is also [a]
.
Then, the signature of (==)
:
(==) :: Eq x => x -> x -> Bool
(note that I replaced a
with x
on purpose: not to confuse it with the a
from reverse
's signature - they are two different a
s, don't have to be the same type)
We can look at this signature as a function that takes an x
and returns another function of type x -> Bool
. So:
(==) :: x -> (x -> Bool)
This can be seen as second argument of bind, the one of type a -> m b
. In order to see it like that, we need to say that a ~ x
and m ~ (->) x
and b ~ Bool
. So the monad in question here is (->) x
- i.e. a "promise" with an input of type x
.
But wait! In order to bind this function to reverse
, they need to be in the same monad! This means that x ~ [a]
. And this in turn means that the type of (==)
gets pinned to:
(==) :: [a] -> ([a] -> Bool)
And so, when we call (>>=)
passing it reverse
as first argument and (==)
as second, we get:
reverse >>= (==)
= \i -> (==) (reverse i) i -- by my definition above
= \i -> reverse i == i