A monad is an applicative functor with some unique features that make it a bit more powerful than either alone. A functor maps a function over some structure; an applicative maps a function that is contained over some structure over some structure and then mappends the two bits of structure. So monads can be thought as just another way of applying functions over structure, with a couple of additional features.
Monad
Following is the definition of monad:
1
2
3
4
5
6
7
8
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
-- another function
(=<<) :: (a -> m b) -> m a -> m b
The return
function is identical to pure
function in applicative.
The (>>)
function is almost the same as the (*>)
function in applicative, for example:
1
2
3
4
5
6
7
putStrLn "Hello, " >> putStrLn "World!"
-- Hello,
-- World!
putStrLn "Hello, " *> putStrLn "World!"
-- Hello,
-- World!
The (>>=)
function is more or less like (*)
in applicative, it reorders the to-be-applied
function and the underlying structure as the argument of the function application. Note the
similarity between ($)
, (<$>)
, (<*>)
, and (>>=)
:
1
2
3
4
5
6
7
($) :: (a -> b) -> a -> b
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
What makes monad so powerful is the function join
introduced in Control.Monad
library:
1
join :: Monad m => m (m a) -> m a
It, in a sense, is a generalization of concat
of Foldable
:
1
concat :: Foldable t => t [a] -> [a]
What Monad is NOT
As monad is widely used, and there are many descriptions on it through different aspects, there are many misunderstanding of monad.
Monad is not
:
- Impure. Monadic functions are pure functions. IO is an abstract datatype that allows for impure, or effectful, actions, and it has a Monad instance. But there’s nothing impure about monads.
- An embedded language for imperative programming. Simon Peyton-Jones, one of the lead developers and researchers of Haskell and its implementation in GHC, has famously said, “Haskell is the world’s finest imperative programming language,” and he was talking about the way monads handle effectful programming. While monads are often used for sequencing actions in a way that looks like imperative programming, there are commutative monads that do not order actions.
- A value. The typeclass describes a specific relationship between elements in a domain and defines some operations over them. When we refer to something as “a monad,” we’re using that the same way we talk about “a monoid,” or “a functor.” None of those are values.
- About strictness. The monadic operations of bind and return are nonstrict. Some operations can be made strict within a specific instance.
Lift
The Monad class also includes a set of lift functions that are the same as the ones in Applicative. They don’t really do anything different, but they are still around because some libraries used them before applicatives were discovered, so the liftM set of functions still exists to maintain compatibility.
1
2
3
4
5
6
7
8
liftA :: Applicative f => (a -> b) -> f a -> f b
liftM :: Monad m => (a1 -> r) -> m a1 -> m r
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
liftM3 :: Monad m => (a1 -> a2 -> a3 -> r) -> m a1 -> m a2 -> m a3 -> m r
Similar to applicative, the fmap
also has some relationship with return
and >>=
of
monad:
1
fmap f xs = xs >>= return . f
And more:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- consider
fmap :: Functor f => (a -> f b) -> f a -> f (f b)
let appendOne x = [x, 1]
:t fmap appendOne [4, 5, 6]
-- fmap appendOne [4, 5, 6] :: Num t => [[t]]
fmap appendOne [4, 5, 6]
-- [[4,1],[5,1],[6,1]]
-- and for >>=
[4, 5, 6] >>= appendOne
-- [4, 1, 5, 1, , 1]
We can see that with (>>=)
the result smashes the nested structures into a flatten pattern,
which differentiates from fmap
keeps the structures in the result. Later, it will be
demonstrated that these two have significant difference, of course it has.
do syntax
Look at the following examples:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sequencing :: IO ()
sequencing = do
putStrLn "Hello, "
putStrLn "World"
sequencing' :: IO ()
sequencing' =
putStrLn "Hello, " >>
putStrLn "World"
sequencing'' :: IO ()
sequencing'' =
putStrLn "Hello, " *>
putStrLn "World"
binding :: IO ()
binding = do
name <- getLine
putStrLn name
binding' :: IO ()
binding' =
getLine >>= putStrLn
We can see that do syntax is a syntactic sugar for monad functions. In the second example,
instead of naming the variable and passing that as an argument to the next function, we use
(>>=)
to pass it.
Only (<$>)
is not enough
As mentioned above, by using fmap
can achieve similar effect as >>=
, but it is not close
enough though. Look at the following example:
1
putStrLn <$> getLine
It waits the input, but it never prints, which means that the IO action of getLine has been evaluated and the IO action of putStrLn not.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
getLine :: IO String
putStrLn :: String -> IO ()
-- The type of fmap
<$> :: Functor f => (a -> b) -> f a -> f b
-- The (a -> b) is putStrLn
-- (a -> b ) a b
-- | | |
putStrLn :: String -> IO ()
-- The f a is getLine
-- f a f a
-- | | |
getLine :: IO String
-- so that
-- f b
-- | |
putStrLn <$> getLine :: IO (IO ())
-- we can further decompose the process
-- the type of putStrLn <$> x is
f :: Functor f => f String -> f (IO ())
-- String f (IO ())
-- | |
f x = putStrLn <$> x
-- the type of x <$> getLine is
g :: (String -> b0) -> IO b0
-- (String -> b0) IO b0
-- | |
g x = x <$> getLine
-- here b0 is IO () in f :: Functor f => f String -> f (IO ())
-- so that
putStrLn <$> getLine :: IO (IO ())
-- | | |
-- [1] [2] [3]
- This outermost IO structure represents the effects getLine must perform to get a String that the user typed in.
- This inner IO structure represents the effects that would be performed if putStrLn was evaluated.
- The unit here is the unit that putStrLn returns.
One of the strengths of Haskell is that we can refer to, compose, and map over effectful computations without performing them or bending over backwards to make that pattern work.
For example of waiting to evaluate IO actions (or any computation in general really):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let printOne = putStrLn "1"
let printTwo = putStrLn "2"
let twoActions = (printOne, printTwo)
:t twoActions
-- twoActions :: (IO (), IO ())
fst twoActions
-- 1
snd twoActions
-- 2
fst twoActions
-- 1
Note that we are able to evaluate IO actions multiple times.
In order to make putStrLn <$> getLine
work as originally expected, we need to join those
two IO layers together:
1
join $ putStrLn <$> getLine
order matters
In the above example, join
merges the effects of getLine and putStrLn into a single IO action.
This merged IO action performs the effects in the “order” determined by the nesting of the IO
actions. As it happens, the cleanest way to express “ordering” in a lambda calculus without
bolting on something unpleasant is through nesting of expressions or lambdas.
Sometimes it is valuable to suspend or otherwise not perform an IO action until some determination
is made, so types are like IO (IO ())
aren’t necessarily invalid, but you should be aware of
what’s needed to make this example work.
Following is an example to desugar do syntax:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
twoBinds :: IO ()
twoBinds = do
putStrLn "name pls:"
name <- getLine
putStrLn "age pls:"
age <- getLine
putStrLn ("y helo thar: " ++ name ++ " who is: " ++ age ++ " years old.")
twoBinds' :: IO ()
twoBinds' =
putStrLn "name pls:" >>
getLine >>=
(\name ->
putStrLn "age pls:" >>
getLine >>=
(\age ->
putStrLn ("y helo thar: " ++ name ++ " who is: " ++ age ++ " years old.")))
do syntax, applicative, and monad
If the do syntax looks like:
1
2
3
4
5
doSomething = do
a <- f
b <- g
...
return (zed a b ...)
You can rewrite it using Applicative. On the other hand, if it looks like:
1
2
3
4
5
doSomething = do
a <- f
b <- g
...
zed a b ...
You’re going to need Monad because zed is producing more monadic structure, and you’ll
need join
to crunch that back down. And in this case, the final result can rely on the
previous results.
Law
Monad instances must abide by the following laws:
1
2
3
4
5
6
7
8
-- identity
-- right identity
m >>= return = m
-- left identity
return x >>= f = f x
-- associative
(m >>= f) >>= g = m >>= (\x -> f x >>= g)
Kleisli composition
With Functor and Applicative, the concerned functions are the usual (a -> b)
arrangement,
so composition just works. It is also guaranteed by the laws of those typeclasses:
1
2
3
fmap id = id
-- guarantees
fmap f . fmap g = fmap (f . g)
The composition of monad can be defined as:
1
2
mcomp :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
mcomp f g a = join (f <$> (g a))
Using join
and <$>
means, we can use >>=
instead:
1
2
mcomp' :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
mcomp' f g a = g a >>= f
There is another kind of function composition, which enables us to compose >>=
, called
Kleisili composition
. To get Kleisli composition off the ground, we have to flip some
arguments around to make the types work:
1
2
3
4
5
6
7
8
-- notice the order is flipped to match >>=
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
-- similar to
flip (.) :: (a -> b) -> (b -> c) -> a -> c
-- where
flip :: (a -> b -> c) -> b -> a -> c
It’s function composition with monadic structure hanging off the functions we’re composing.