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
Law | definition (>>=) | definition (>=>) | definition join / j |
---|---|---|---|
left identity | return a >>= f = f a | return >=> g == g | join . return == id |
right identity | m >>= return = m | f >=> return == f | join . fmap pure == id |
associativity | (m >>= f) >>= g = m >>= (\x -> fx >>= g) | f >=> (g >=> h) | j . fmap j = j . j |
Applicatives
Law | definition |
---|---|
Identity | pure id <*> v = v |
Homomorphism | pure f <*> pure x = pure (f x) |
Interchange | u <*> pure y = pure ($ y) <*> u |
Composition | pure (.) <*> 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:
- If we can get a result, pass it into f.
- 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