简体   繁体   中英

Haskell `palindrome = reverse >>= (==)`

Can someone explain how this function for palindrome in Haskell works:

palindrome :: Eq a => [a] -> Bool
palindrome = reverse >>= (==)

-- type declarations
reverse :: [a] -> [a]
>>= :: Monad m => m a -> (a -> m b) -> m b
(reverse >>=) :: ([a] -> [a] -> b) -> [a] -> b
(==) :: Eq a => a -> a -> Bool

In particular, how does the definition of the Monad typeclass on functions work, and how does it somehow reduce the number of inputs for (==) from two to one list?

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 -> mb . 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 - ie 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

I think this will be an extension to a previous answer of mine in which i had explained how is a function a functor and applicative. Let me start with the same sentence.

We may consider functions ( (->) r ) as a context with a contained value revealed once applied. Here we have reverse function which contains a reversed list but we don't get it until we apply it to a list. Let's remember the type signature of bind.

>>= :: Monad m => m a -> (a -> m b) -> m b

Here ma is reverse function and the a inside m is the reversed list. (==) is the (a -> mb) and mb is a -> Bool . So >>= returns an mb which is just a function which takes a list and returns a Bool . This is consistent because we are in the function monad so it takes a monad (function) and returns another one. Only when we invoke the returned one with a list the whole mechanism runs. Both ma and mb gets applied to the provided list.

Manually implementing the monad instance of (->) r would be like

return x = \_ -> x -- aka const
f >>= g = \r -> g (f r) r

If we look into this carefully we notice that >>= is actually the flipped version of <*> for (->) r type. But be careful when i say fllipped it doesnt mean g <*> f == f >>= g .

g <*> f = \x -> g x (f x)
f >>= g = \x -> g (f x) x

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM