Take functions like sequence
, filterM
, liftM2
, join
and think how they work for each monad IO
, []
, (->) a
, Writer
, State
. For example, sequence
for IO monad executes IO actions sequentially:
[IO a] -> IO [a]
Write the signature and try to use them. Some combinations are interesting, some less.
Example for filter:
{-# LANGUAGE NoMonomorphismRestriction #-}
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
a = filterM (\x -> do putStrLn $ "Put " ++ show x ++ "?"
fmap (=="Y") getLine)
b = filterM (const [False,True])
c m xs = runState (filterM k xs) 0
where k x = do a <- get
let p = a + x <= m
when p $ put (a+x)
return p
d = filterM (flip id)
a
filters the list using IO - it asks the user about each item.
b
filters the list nondeterministically - every item is nondeterministically included and not included. As a result, you get powerset. (Try it!)
c
filters the list, maintaining state. In this case this is greedy knapsack - you've got knapsack of capacity m
and want to insert as many items from xs
.
d
filters the list, maintaining a read-only state. This isn't interesting. I used the filtering function as the state, and this gives flip filter
.
A single function filterM
can do all those things.
If you'll write similar code for other functions, you'll get enough intuition to notice monadic functions in other places. For example, how to get the following function?
dup f x = f x x
and what about dup' f x = f x x x
?