Applicative Computations
I am trying to get a better understanding of applicative computations vs. monadic computations. I read most parts of Applicative Programming with Effects which give a good background even if it is rather academic. This was referred from Typeclassopedia on the topic of Applicatives.
I thought I would try out some concrete examples to help me get a better understanding of this topic.
First I implemented the corresponding method for sequencing applicative computations.
import Control.Applicative
dist :: Applicative f => [f a] -> f [a]
dist [] = pure []
dist (v:vs) = (:) <$> v <*> (dist vs)
It works in a similar manner as sequence
defined in Control.Monad
.
I made some example effectful computations to try it on.
ghci> dist [Just 1, Nothing]
Nothing
ghci> sequence [Just 1, Nothing]
Nothing
I defined the following effectful computations and tried to sequence them.
hello = print "hello"
bye = print "bye"
ghci> sequence [hello, bye]
"hello"
"bye"
[(),()]
ghci> dist [hello, bye]
"hello"
"bye"
[(),()]
One important difference between applicative sequencing and monadic sequencing is that monadic seqencing may alter the sequence of computations based on previous computations.
The following conditional computations show the difference.
miffy :: Monad m => m Bool -> m a -> m a -> m a
miffy mb mt me = do
b <- mb
if b then mt else me
fiffy :: Applicative f => f Bool -> f a -> f a -> f a
fiffy fb ft fe = do
cond <$> fb <*> ft <*> fe where
cond b t e = if b then t else e
The monadic functions only compute one of the effectful computations based on the condition.
ghci> miffy (pure False) hello bye
"bye"
But as can be seen in the applicative variant both effects are present. The applicaative variant can of course return different results based on the condition, but the effect will always be present for all computations.
ghci> fiffy (pure True) hello bye
"hello"
"bye"
I tried a differenct monadic sequencing where the previous computations change the computations of the following computations. This operation cannot be done using applicative sequencing.
mop :: (Monad m, Integral a) => a -> m a
mop a = if (a > 0) then return (a+1) else return (a-1)
ghci> (return 1) >>= mop >>= mop
3
ghci> (return 0) >>= mop >>= mop
-2