Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Monad stacks do wonders for circumscribing your computational context in an app.

Wait until you discover the power of Applicative Functors. They are more restricted in their operations than Monads, so they compose better and allow for analysis.



Mathematically speaking applicatives are less restrictive since more things can be applicatives than monads. A monad is a "very special" kind of functor and that is why a composition of monads often fails to satisfy the monad laws. The least restricted thing in this hierarchy is the functor since composition and other kinds of operations always give you back another functor.


I'm curious, what do you mean they compose better and allow for analysis [better than monads]?


You can determine a lot more about a computation described only in terms of an applicative functor as you can from a monadic one, without running any of its effects (i.e. "statically").

It's easy to see why when you consider the opaqueness of the function in the right-hand side of a bind. Check out http://gergo.erdi.hu/blog/2012-12-01-static_analysis_with_ap... for an example.


gergoerdi covered analysis, but composition is simple. For any two Applicatives F and G and value type A the following 4 values are all Applicative values

    F A
    G A
    F (G A)
    G (F A)
But for monads only the first two are monads for general monads F and G. So, Applicatives compose better!


And to get F (G A) with Monads, F needs to be a Monad transformer.

In addition to composition, products of Applicatives are also Applicatives:

(F A, G A)


Even for monad transformers it's not necessarily the case that direct composition leads to a monad. For instance, ContT looks like

    newtype ContT r m a = ContT ((a -> m r) -> m r)
so ContT M A is not the same as Cont (M A).

Monads also compose under products as two parallel monadic computations

    data (f * g) a = Pair (f a) (g a)

    p1 :: (f * g) a -> f a
    p1 (Pair fa _) = fa

    p2 :: (f * g) a -> g a
    p2 (Pair _ ga) = ga

    instance (Monad f, Monad g) => Monad (f * g) where
      return a = Pair (return a) (return a)
      Pair fa ga >>= k = Pair (fa >>= p1 . k) (ga >>= p2 . k)
You can also talk about composition under sums

    data (f + g) a = Inl (f a) | Inr (g a)
and you'll get compositions of applicatives here so long as you have a notion of natural transformation

    class Natural f g where
      phi :: f a -> g a

    instance (Applicative f, Applicative g, Natural f g) => Applicative (f + g) where
      pure a = Inl (pure a)
      Inl ff <*> Inl fa = Inl (    ff <*>     fa)
      Inr gf <*> Inr ga = Inr (    gf <*>     ga)
      Inl ff <*> Inr ga = Inr (phi ff <*>     ga)
      Inr gf <*> Inl fa = Inr (    gf <*> phi fa)
but there's no way to get a similar monad. It turns out that you have to "know what branch to go down before you start" in exactly the kind of way applicatives allow and monad preclude.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: