haskelltreemonadsapplicativefree-monad

Are free monads also zippily applicative?


I think I've come up with an interesting "zippy" Applicative instance for Free.

data FreeMonad f a = Free (f (FreeMonad f a))
                   | Return a

instance Functor f => Functor (FreeMonad f) where
    fmap f (Return x) = Return (f x)
    fmap f (Free xs) = Free (fmap (fmap f) xs)

instance Applicative f => Applicative (FreeMonad f) where
    pure = Return

    Return f <*> xs = fmap f xs
    fs <*> Return x = fmap ($x) fs
    Free fs <*> Free xs = Free $ liftA2 (<*>) fs xs

It's sort of a zip-longest strategy. For example, using data Pair r = Pair r r as the functor (so FreeMonad Pair is an externally labelled binary tree):

    +---+---+    +---+---+               +-----+-----+
    |       |    |       |      <*>      |           |
 +--+--+    h    x    +--+--+   -->   +--+--+     +--+--+
 |     |              |     |         |     |     |     |
 f     g              y     z        f x   g x   h y   h z

I haven't seen anyone mention this instance before. Does it break any Applicative laws? (It doesn't agree with the usual Monad instance of course, which is "substitutey" rather than "zippy".)


Solution

  • Yes, it looks like this is a lawful Applicative. Weird!

    As @JosephSible points out, you can read off the identity, homomorphism and interchange laws immediately from the definitions. The only tricky one is the composition law.

    pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
    

    There are eight cases to check, so strap in.

    For the pure (.) <*> Free u <*> Free v <*> Return z case I used a lemma:

    Lemma: fmap f (fmap g u <*> v) = liftA2 (\x y -> f (g x y)) u v.

    fmap f (fmap g u <*> v)
    pure (.) <*> pure f <*> fmap g u <*> v  -- composition
    fmap (f .) (fmap g u) <*> v             -- homomorphism
    fmap ((f .) . g) u <*> v                -- functor law
    liftA2 (\x y -> f (g x y)) u v          -- eta expand
    QED.
    

    Variously I'm using functor and applicative laws under the induction hypothesis.

    This was pretty fun to prove! I'd love to see a formal proof in Coq or Agda (though I suspect the termination/positivity checker might mess it up).