What's the difference between Applicatives and Monads?

Monads

M1 a -> M (M1 a) -> M1 a M1 a -> M1 (M a) -> M a

Things w/o effects can be ignored.

M1 ( M2 ( M3 a ) ) = M1+M2 ( M3 a ) = M1+M2+M3 a M1 ( M2 ( M3 a ) ) = (M1 (M2 + M3) a ) = M1+M2+M3 a

Effects evaluated in different order should be fine ^

M (M a) -> M a

Lawdefinition (>>=)definition (>=>)definition join / j
left identityreturn a >>= f = f areturn >=> g == gjoin . return == id
right identitym >>= return = mf >=> return == fjoin . fmap pure == id
associativity(m >>= f) >>= g = m >>= (\x -> fx >>= g)f >=> (g >=> h)j . fmap j = j . j

Applicatives

Lawdefinition
Identitypure id <*> v = v
Homomorphismpure f <*> pure x = pure (f x)
Interchangeu <*> pure y = pure ($ y) <*> u
Compositionpure (.) <*> u <*> v <*> w = u <*> (v <*> w)

Differences

What is the difference in Applicatives and Monads?

High level understanding:

key difference is in the second argument.

| | Monads (>>=) | Applicatives (<**>) | | full type signature | m a -> (a -> m b) -> m b | f a -> f (a -> b) -> f b | | second argument | a -> m b | f (a -> b) | | difference | First argument, a can decide the future computation m | a is already in the context f |

Examples for intuition

Error collecting

Monadic instance for Either:

instance Monad (Either e) where
  return = Right
  v@(Left e) >>= _ = v
  Right a >>= f = f a

We observe that:

Left "Failed 0" >>= \_ -> Left "Failed 1" = Left "Failed 0"

We have lost the information: Left "Failed 1"

We might then be tempted to tweak the monad definition, to permit collecting error messages:

instance Monoid e => Monad (Either e) where
  return = Right
  Left e >>= f = ??
  Right a >>= f = f a

What do we want to do? We want to apply f to a value: f a. We can then pattern match on the result:

Left e >>= f = case f a of
  Left e' -> Left (e <> e')
  Right _ -> Left e

We realize that we can’t do that however. Left e is part of the context and does not return any result, in contrast to Right a.

Since f only takes in a as a functional dependency (since f :: a -> m b) The context of the computation (f a in f a -> (a -> m b)) is opaque to it.

As such we can only do 2 things:

  1. If we can get a result, pass it into f.
  2. If we can’t get a result, short circuit, since we can’t evaluate f in any way.

Wait a minute, you say.

What if we did this:

Left e >>= \_ -> Left e' = Left (e <> e')

We can’t do that either because we cannot go into function bodies! We can only see the mapping between input and the result.

What about this then!

Left e >> Left e = Left (e <> e')

Yes we could do that.

Note however, this is not powerful enough to give us a monad.

We are unable to define (>>=) in terms of (>>) (although the vice-versa works).

(Possible) Applicative instance for Either:

instance Monoid e => Applicative (Either e) where
  pure = Right
  Left e <*> Left e' = Left (e <> e')
  Left e <*> Right a = Right a
  Right f <*> Left e' = Left e'
  Right f <*> Right a = Right $ f a

This is because Applicative interface separate the context layer (f (a -> b)) rather than (a -> f b).

This makes the (<*>) operator able to work on the contexts as well.

Further examination of applicative patterns

Let’s examine two types of computations:

Here, we can see that p1, p2 are not dependent on previous result. The final result uses return which means it does not introduce any new context.

do val1 <- p1
   val2 <- p2
   return $ f val1 val2

We can thus represent this in applicative form:

pure f <*> val1 <*> val2

Here we can see a dependence:

do val1 <- p1
   val2 <- p2 val1 -- p2 is dependent on the result of p1
   return val2

Thus, this requires a monad.

Closed under composition

m1 (m2 a) may or may not be a monad even if m1, m2 are monads.

a1 (a2 a) is an applicative as long as a1, a2 are applicatives.

References: Stack Overflow