Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it permissible to use `ap` on a function that takes two monad values as opposed to a function wrapped in the monad?

Tags:

haskell

monads

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.

like image 498
Ed Lim Avatar asked Oct 27 '25 08:10

Ed Lim


1 Answers

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.

like image 81
MMZK1526 Avatar answered Oct 29 '25 09:10

MMZK1526



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!