Composing Monads v. Applicative Functors

前端 未结 1 1093
失恋的感觉
失恋的感觉 2020-11-29 04:36

The Typeclassopedia\'s Monad Transformers section explains:

Unfortunately, monads do not compose as nicely as applicative functors (yet anoth

相关标签:
1条回答
  • 2020-11-29 05:07

    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 )
    
    0 讨论(0)
提交回复
热议问题