Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Composing Monads v. Applicative Functors

Tags:

haskell

The Typeclassopedia's Monad Transformers section explains:

Unfortunately, monads do not compose as nicely as applicative functors (yet another reason to use Applicative if you don’t need the full power that Monad provides)

Looking at the types of >>= and <*>, the above statement is not clear to me.

(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b

Please explain the "monads do not compose as nicely as applicative functors."

I read this answer, but could you please give an example to help me understand?

like image 939
Kevin Meredith Avatar asked Apr 05 '15 03:04

Kevin Meredith


People also ask

Are all monads applicative functors?

As I understand, every monad is a functor but not every functor is a monad. A functor takes a pure function (and a functorial value) whereas a monad takes a Kleisli arrow, i.e. a function that returns a monad (and a monadic value).

Could you comfortably explain the difference between a monad and an applicative functor?

Functors apply a function to a wrapped value: Applicatives apply a wrapped function to a wrapped value: Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.

Are monads functors?

Functor is the simplest pattern, so it makes sense to start there. As you work your way to monad, you'll see that functor is the basis for applicative functor which is the basis for monad.

Are all monads applicative?

Monads are not a replacement for applicative functors Instead, every monad is an applicative functor (as well as a functor). It is considered good practice not to use >>= if all you need is <*>, or even fmap.


1 Answers

There are several notions by which types of kind * -> * might "compose". The more important one is you can compose them "sequentially".

newtype Compose f g x = Compose { getCompose :: f (g x) }

Here you can see that Compose has kind (* -> *) -> (* -> *) -> (* -> *) much like any good composition of functors ought to.

So the question is: are there law-abiding instances like the following?

instance (Applicative f, Applicative g) => Applicative (Compose f g)
instance (Monad f,       Monad g)       => Monad       (Compose f g)

And the short answer as to why monads don't compose as well as applicatives is that while the first instance can be written the second cannot. Let's try!


We can warm up with Functor

instance (Functor f,     Functor g)     => Functor     (Compose f g) where
  fmap f (Compose fgx) = Compose (fmap (fmap f) fgx)

Here we see that because we can fmap an fmap-ed f we can pass it through the layers f and g like we need to. A similar game is played with pure

instance (Applicative f, Applicative g) => Applicative (Compose f g) where
  pure a = Compose (pure (pure a))

and while (<*>) appears tricky, if you look carefully it's the exact same trick we used with both fmap and pure.

  Compose fgf <*> Compose fgx = Compose ((<*>) <$> fgf <*> fgx)

In all cases, we can push the operators we need "through" the layers of f and g exactly as we might hope.

But now let's take a look at Monad. Instead of trying to define Monad via (>>=), I'm going to instead work via join. To implement Monad we need to implement

join :: Compose f g (Compose f g x) -> Compose f g x

using

join_f :: f (f x) -> f x  -- and
join_g :: g (g x) -> g x

or, if we strip off the newtype noise, we need

join :: f (g (f (g x))) -> f (g x)

At this point it might be clear what the problem is---we only know how to join consecutive layers of fs or gs, but here we see them interwoven. What you'll find is that we need a commutativity property

class Commute f g where
  commute :: g (f x) -> f (g x)

and now we can implement

instance (Monad f, Monad g, Commute f g) => Monad (Compose f g)

with (the newtype agnostic) join defined as

join :: f (g (f (g x))) -> f (g x)
join fgfgx = fgx where
  ffggx :: f (f (g (g x)))
  ffggx = fmap commute fgfgx
  fggx :: f (g (g x))
  fggx = join_f ffggx
  fgx :: f (g x)
  fgx = fmap join_g fggx

So what's the upshot of all this? Applicatives always Compose, but Monads Compose only when their layers Commute.

When can we commute layers? Here are some examples

instance Commute ((->) x) ((->) y) where
  commute = flip

instance Commute ((,) x) ((,) y) where
  commute (y, (x, a)) = (x, (y, a))

instance Commute ((->) x) ((,) y) where
  commute (y, xa) = \x -> (y, xa x)

-- instance Commute ((,) x) ((->) y) does not exist; try to write yourself!
--
-- OR:
-- It turns out that you need to somehow "travel back in time" to make it
-- work...
-- 
-- instance Commute ((,) x) ((->) y) where
--   commute yxa = ( ..., \y -> let (x, a) = yxa y in a )
like image 192
J. Abrahamson Avatar answered Sep 28 '22 03:09

J. Abrahamson