Here's the haskell code:
zipWith compare `ap` tail
where
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
compare :: a -> a -> Ordering
ap :: Monad m => m (a -> b) -> m a -> m b
So from what I understand ap in this case will need a function wrapped inside the list monad, that is it needs m (a -> b), which is supposedly fulfilled by zipWith compare but I don't see how, because zipWith compare will take on type [a] -> [a] -> [Ordering] which is not quite correct. Is there something happening behind the scenes of the list monad that causes the necessary type conversion?
The code works and is not my own work, I'm just trying to understand how it's permissible.
The trick here is that a -> is a Monad!
In fact, we can list out the instances of Monad in a plain ghci session:
ghci> :i Monad
type Monad :: (* -> *) -> Constraint
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
{-# MINIMAL (>>=) #-}
-- Defined in ‘GHC.Base’
instance Monoid a => Monad ((,) a) -- Defined in ‘GHC.Base’
instance (Monoid a, Monoid b) => Monad ((,,) a b)
-- Defined in ‘GHC.Base’
instance (Monoid a, Monoid b, Monoid c) => Monad ((,,,) a b c)
-- Defined in ‘GHC.Base’
instance Monad ((->) r) -- Defined in ‘GHC.Base’ <- This is the one!
instance Monad IO -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Monad Solo -- Defined in ‘GHC.Base’
instance Monad [] -- Defined in ‘GHC.Base’
instance Monad (Either e) -- Defined in ‘Data.Either’
Therefore, zipWith compare :: m ([a] -> Ordering) and tail :: m [a] where m is ((->) [a]), or ([a] ->).
The trick here is that the function arrow, ->, is indeed a type constructor that builds new types. Once getting around this, you can take a look at the actual implementation of its Monad instance (and it's nothing out of expectation there).
In fact, for ap (I haven't double-checked but I'm pretty confident), it looks like this:
ap :: (r -> (a -> b)) -- this is m (a -> b)
-> (r -> a) -- this is m a
-> (r -> b) -- this is m b
ap rab ra
= \r -> -- the result must be a function starting from r
(rab r) -- gives (a -> b)
(ra r) -- gives a, thus the body of the lambda gives b
This is also the S-combinator in SKI.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With