How to handle side effect with Applicative?
Applicative and Monad both provide ways of "combining" multiple side-effectful1 values into a single side-effectful value.
The Applicative interface for combining just lets you combine effectful values such that the resulting effectful value combines all their effects according to some "fixed" recipe.
The Monad interface for combining lets you combine effectful values in such a way that the effects of the combined value depends on what the original effectful values do when they're actually resolved.
For example, the State Integer
monad/applicative is of values that depend upon (and affect) some Integer
state. State Integer t
values only have a concrete value in the presence of that state.
A function that takes two State Integer Char
values (call them a
and b
) and gives us back a State Integer Char
value and only uses the Applicative interface of State Integer
must produce a value whose "statefulness" is always the same, regardless of what the Integer
state value is and regardless of what Char
values the inputs yield. For example, it could thread the state through a
and then b
, combining their Char
values somehow. Or it could threat the state through b
and then a
. Or it could pick only a
or only b
. Or it could ignore both entirely, not taking either of their effects on the current Integer
state, and just pure
some char value. Or it could run either or both of them any fixed number of times in any fixed order, and it could incorporate any other State Integer t
values it knows about. But whatever it does, it always does that, regardless of the current Integer
state, or any values produced by any of the State Integer t
values it manages to get its hands on.
A function that took the same inputs but was able to use the monad interface for State Integer
can do much more than that. It can run a
or b
depending on whether the current Integer
state is positive or negative. It can run a
, then if the resulting Char
is an ascii digit character it can turn the digit into a number and run b
that many times. And so on.
So yes, a computation like:
do
print' "hello"
print' "world"
Is one that could be implemented using only the Applicative interface to whatever print'
returns. You are close to correct that the difference between Monad and Applicative if both had a do-notation would be that monadic do would allow x <- ...
, while applicative do would not. It's a bit more subtle than that though; this would work with Applicative too:
do x <- ...
y <- ...
pure $ f x y
What Applicative can't do is inspect x
and y
to decide what f
to call on them (or do anything with the result of f x y
other than just pure
it.
You are not quite correct that there's no difference between Writer w
as a monad and as an applicative, however. It's true that the monadic interface of Writer w
doesn't allow the value yielded to depend on the effects (the "log"), so it must always be possible to rewrite any Writer w
defined using monadic features to one that only uses applicative features and always yields the same value2. But the monadic interface allows the effects to depend on the values, which the applicative interface doesn't, so you can't always faithfully reproduce the effects of a Writer w
using only the applicative interface.
See this (somewhat silly) example program:
import Control.Applicative
import Control.Monad.Writer
divM :: Writer [String] Int -> Writer [String] Int -> Writer [String] Int
divM numer denom
= do d <- denom
if d == 0
then do tell ["divide by zero"]
return 0
else do n <- numer
return $ n `div` d
divA :: Writer [String] Int -> Writer [String] Int -> Writer [String] Int
divA numer denom = divIfNotZero <$> numer <*> denom
where
divIfNotZero n d = if d == 0 then 0 else n `div` d
noisy :: Show a => a -> Writer [String] a
noisy x = tell [(show x)] >> return x
Then with that loaded in GHCi:
*Main> runWriter $ noisy 6 `divM` noisy 3
(2,["3","6"])
*Main> runWriter $ noisy 6 `divM` noisy 0
(0,["0","divide by zero"])
*Main> runWriter $ undefined `divM` noisy 0
(0,["0","divide by zero"])
*Main> runWriter $ noisy 6 `divA` noisy 3
(2,["6","3"])
*Main> runWriter $ noisy 6 `divA` noisy 0
(0,["6","0"])
*Main> runWriter $ undefined `divA` noisy 0
(0,*** Exception: Prelude.undefined
*Main> runWriter $ (tell ["undefined"] *> pure undefined) `divA` noisy 0
(0,["undefined","0"])
Note how with divM
, whether numer
's effects are included in numer `divM` denom
depends on the value of denom
(as does whether the effect of tell ["divide by zero"]
). With the best the applicative interface can do, the effects of numer
are always included in numer
divAdenom
, even when lazy evaluation should mean that the value yielded by numer
is never inspected. And it's not possible to helpfully add "divide by 0" to the log when the denominator is zero.
1 I don't like to think of "combining effectful values" as the definition of that monads and applicatives do, but it's an example of what you can do with them.
2 When bottoms aren't involved, anyway; you should be able to see from my example why bottom can mess up the equivalence.
Output
The applicative equivalent for >>
is *>
, so you can do
ghci> :m Control.Applicative
ghci> print 5 *> print 7
5
7
Input - a better case for Applicative
import Control.Applicative
data Company = Company {name :: String, size :: Int}
deriving Show
getCompany :: IO Company
getCompany = Company <$> getLine <*> readLn
Which works nicely for input:
ghci> getCompany >>= print
BigginsLtd
3
Company {name = "BigginsLtd", size = 3}
Notice that since we're using Applicative for IO, we're in the IO monad anyway, so can use >>=
if we like. The benefit Applicative gives you is the nice syntax.
My favourite is with parsing, so I can do
data Statement = Expr Expression | If Condition Statement Statement
parseStatement = Expr <$> parseExpression <|>
If <$> (string "if" *> parseCondition)
<*> (string "then" *> parseStatement)
<*> (string "else" *> parseStatement)
The difference between Applicative and Monad
The difference between Applicative and Monad is that Monad has >>=
, which lets you choose what side effect to use based on the value you have.
Using Monad:
don't_reformat_hard_drive :: Bool -> IO ()
don't_reformat_hard_drive yes = if yes then putStr "OK I didn't"
else putStr "oops!" >> System.IO.reformat "C:/"
maybeReformat :: IO ()
maybeReformat = WinXP.Dialogs.ask "Don't reformat hard drive?"
>>= don't_reformat_hard_drive
(There's no System.IO.reformat
or WinXP.Dialogs.ask
. This is just an example I found funny.)
Using Applicative:
response :: Bool -> () -> String
response yes () = if yes then "OK I didn't" else "oops!"
probablyReformat = response <$> WinXP.Dialogs.ask "Don't reformat hard drive?"
<*> System.IO.reformat "C:\"
Sadly, using Applicative I can't inspect the Boolean value to determine whether to reformat or not – the side effect order is determined at compile time, in an Applicative, and the hard drive will always be reformatted with this piece of code. I need the Monad's bind (>>=
) to be able to stop the reformat.
.........your hard drive C: has been successfully reformatted.
"OK I didn't"