Much ado about Monads
- Dictionary
- A utilitarian definition of Monads
- Maybe
- List
- Either
- Writer
- Reader
- State
- IO
- Acknowledgements
- All the code
This is a minimal tutorial on Monads i.e. the mechanics rather than the philosophy. I’m not going to convince you that they’re important or beautiful or magical or powerful. On the other hand I will try to convince you that they are simple, that they’re not complicated, abstruse, or impenetrable. All you have to do is unwrap all of the indirection.
Also the explanation will be morally correct rather than formally/technically correct. So while the code does type check and compile (at least as of GHC 8.4.3) and I think I haven’t violated any of the Monad laws, these are not the actual definitions/implementations. Consequently I’m not going to cover Monad transformers.
Strong recommendation
Reduce all expressions to their most basic forms by hand (maybe on paper) in order to really understand what’s going on.
That means transform each use of >>=
and return
into their definition in the Monad.
Note you need to include
import Prelude hiding (Monad(..))
at the top of your script in order to shadow default implementations if you plan to follow along.
Dictionary
The only syntax/concepts you need to understand
syntax | definition | parallel |
---|---|---|
foo :: Int |
Int type value foo |
Types in any statically type language |
foo :: Bar Int |
Algebraic data type Bar composed with type Int |
no clue (this is algebraic data types) |
foo :: Int -> String |
Type signature of function foo from type Int to type String |
Types in any statically type language |
foo :: Int -> Int -> String |
Type signature of two argument function foo from type Int, Int to type String |
Types in any statically type language |
foo :: a -> b |
Type signature of generic foo from any type a to any type b |
Types in any statically type language with generics |
class Foo m where |
Parametric type class Foo with type parameter m |
public interface Foo in Java |
class Baz m => Foo m where |
Parametric type class Foo with type parameter m with bounded type m |
public class Moo implements Foo, Baz in Java |
(+) |
“prefixification” of an infix operator + |
no clue |
instance Foo Bar where |
Instantiation of type class Foo by type Bar |
public class Bar implements Foo |
instance Foo (Bar e) where |
Instantiation of type class Foo by parametric type Bar |
public class Bar<E> implements Foo |
instance Baz e => Foo (Bar e) where |
Instantiation of type class Foo by parametric type Bar with bounded type e |
public class Bar<e extends Bar> implements Foo |
\x -> 2*x |
Lambda function with parameter x |
lambda x: 2*x in python |
f x |
f applied to x |
f(x) |
f $ g x |
f applied to g applied to x (saves parentheses in f (g x) ) |
f(g(x)) |
f . g $ x |
f composed with g |
f(g(x)) |
data Env = Env { foo :: String, bar :: Int} deriving (Show) |
Record | Structs in C++ with default cout << representation (note that Env on the left side of = is different from Env on the right - left is type, right is the constructor [and they could be named distinctly] ) |
You also need to understand algebraic data types but I’m not going to explain that.
A utilitarian definition of Monads
A Monad
is a type class whose instances implement two functions/operators: >>=
and return
(note return
is a function not a keyword)
which themselves abide by the Monad laws (though you should skip reading these until you finish this post).
class Monad mon where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
>>=
takes a value of type m a
(i.e. a value of the type that implements the interface) on the left and a function with signature k :: a -> m b
on the right and then in toto returns a new value of the type m b
. The function k :: a -> m b
takes a value of type a
and returns a value of type m b
.
You can think of this function k
as a callback.
return
takes a value of type a
and returns a type of m a
.
Keep in mind that each Monad instance stands or falls on its own merits and that the only thing in common between all of them is that they implement these functions.
Maybe
The simplest Monad to reason about is Maybe
:
instance Monad Maybe where
m >>= k = case m of
(Just x) -> k x
Nothing -> Nothing
return = Just
What does this do? If m
is the Just
variant of Maybe
then >>=
just passes the wrapped value x
on to the callback k
.
Otherwise (if m
is the Nothing
variant) >>=
doesn’t pass anything and doesn’t even call the callback.
In a phrase: Monad Maybe
is for sequences of functions each of which can fail.
An illustration:
trivial :: Maybe Int -> Maybe Int
trivial monkeyWrench =
Just 3 >>= \a ->
monkeyWrench >>= \b ->
return $ a + b
obvSuccess = trivial $ Maybe 5
-- obvSuccess == Just 8
obvFail = trivial $ Nothing
-- obvFail == Nothing
The heuristic way to think about the composition Just 3 >>= \a ->
is that the “inner” value in the Just
is bound to the a
.
I’ll point out that this would be useful, for example, in python where, if you scrape using BeautifulSoup, you end up writing things like
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
maybe_div = soup.find('div')
if maybe_div is not None:
maybe_tr = maybe_div.find('tr')
if maybe_tr is not None:
maybe_td = maybe_tr.find('td')
if ...
List
The List
Monad instance “models” multiple results.
instance Monad [] where
m >>= f = concat (map f m)
return x = [x]
m :: [Int]
m = [1, 2, 3, 4]
f :: Int -> [Int]
f = \x -> [x, 2*x]
sameAndDouble = m >>= f
-- [1,2,2,4,3,6]
Not the most useful thing in the world with one application but with two applications you can, for example, compute cartesian product:
cartProduct = [1,2,3] >>= \x ->
[4,5,6] >>= \y ->
return (x,y)
-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
Either
Either
is basically exactly the same as Maybe
.
import Data.Either
instance Monad (Either e) where
m >>= k = case m of
(Right x) -> k x
(Left e) -> Left e
return = Right
Only difference is that Either
is simply more useful (in that it passes on a message when it short-circuits).
trivialEither :: Either String Int -> Either String Int
trivialEither monkeyWrench =
Right 3 >>= \a ->
monkeyWrench >>= \b ->
return $ a + b
obvSuccess = trivialEither $ Right 5
-- obvSuccess == Right 8
obvFail = trivialEither $ Left "nbd"
-- obvFail == Left "nbd"
Writer
Writer
is the first of the slightly more advanced Monads.
The first thing to notice is that the type that instantiates the Monad is ((,) w)
. The type here is the tuple type
construtor partially applied i.e. (w,)
. Meaning for a particular w
(that itself instantiates Monoid
[a Monoid is any type that implements mappend
and mempty
, mappend
combines values of the type and mempty
is the unit of the type, like 0 for integers]) and arbitrary a
the type (w, a)
is a Monad according to the following implementations
of >>=
and return
:
instance Monoid w => Monad ((,) w) where
return a = (mempty, a)
m >>= k =
let (w, a) = m
(w', b) = k a
in (w `mappend` w', b)
All >>=
does is combine the w
value from m
with the w'
value produced by the callback.
A simple use case of Writer
is logging: the first entry w
in the tuple (w,a)
serves as the log and the second
entry a
serves as the result of some operation. To make adding to the log ergonomic there’s a utility method tell
tell :: w -> (w, ())
tell w = (w, ())
Here’s an example:
type MyWriter = ([Int], String)
example :: MyWriter
example =
tell [1,2,3] >>= \_ ->
tell [3,4,5] >>= \_ ->
return "foo"
example
logs the the arrays [1,2,3]
and [3,4,5]
as a side-effect of producing the value "foo"
Reader
Reader
is in a way the opposite of Writer
: where as Writer
implements threading write only state through a sequence
of function calls (think about it - you can only append to the log), Reader
implements threading read only state through
a sequence of function calls.
instance Monad ((->) r) where
return a = (\_ -> a)
m >>= k = \r -> (k (m r)) r
Again the type that’s instantiating Monad is a little strange: it’s the partially applied function type constructor (->)
.
This means that for a fixed “input” type r
and arbitrary “output” type a
the type (r -> a)
is a Monad according to
the foregoing implementations of >>=
and return
.
What do >>=
and return
do? >>=
passes r
to m
(which reads r
and produces some value) but also passes r
to
the callback (as well as passing the result of m
). return
injects a value into the sequence of function calls while ignoring r
.
The use case is reading from some kind of environment object. Imagine needing to access some kind of environment/configuration
object in a sequence of function calls and being unable to store that environment/configuration in global scope. What would you do?
You would simply pass it as a trailing argument to each of the functions. This is exactly what Reader
does.
In order to make accessing the environment/configuration object easier there are some utility methods:
-- just returns the environment
ask :: a -> a
ask = id
-- transforms the env (usually used to access member in record)
asks :: (r -> a) -> (r -> a)
asks f = f
-- Executes a computation in a modified environment.
local :: (r -> r) -- function to modify env
-> (r -> a) -- reader to run in the modified env
-> (r -> a)
local f m = m . f
which are used as such
data MyContext = MyContext
{ foo :: String
, bar :: Int
} deriving (Show)
updateContext :: MyContext -> MyContext
updateContext m = m { foo = "foo" }
computation :: MyContext -> (Maybe String)
computation = asks bar >>= \n ->
local updateContext (asks foo) >>= \x ->
if n > 0 then
return (Just x)
else
return Nothing
ex1 :: Maybe String
ex1 = computation $ MyContext "hello" 1
ex2 :: Maybe String
ex2 = computation $ MyContext "haskell" 0
State
State
is essentially both Writer
and Reader
except the state is now mutable and honestly would probably be better
called StateTransformer
(but that name is already taken by StateT
, the state monad transformer). Unfortunately (due to compiler limitations)
expressing State
is a little messier than the preceding Monads insofar as it requires newtype
. This just a thin wrapper
and can be unwrapped using pattern-matching/destructuring. If it helps (it did for me!) just pretend State
doesn’t appear.
newtype State s a = State (s -> (a,s))
instance Monad (State s) where
return a = State $ \s -> (a, s)
sm >>= k = State $ \s ->
let (State st) = sm
(a, s') = st s
(State st') = k a
in st' s'
The type that is monadic is s -> (a,s)
i.e. functions that transform state of type s
and simultaneously produce results of type a
.
>>=
extracts the state transforming function st
wrapped up in sm
, applies it to the current state, calls the callback on the result
in order to produce another state transformer st'
, and then applies that state transformer st'
to the as of then current state s'
. The point is that state is passed automatically between sm
and the callback (just like environment is passed automatically by Reader
).
Note that >>=
must produce a state transformer itself (wrapped up in the State
constructor) and hence is implemented as a lambda.
return
produces the identity state transformer that just passes the state through without mutation but decorates it with
some value of type a
.
In order to help read/write state there are a couple of utility functions:
-- get the current state
get :: State s s
get = State $ \s -> (s, s)
-- replace the current state
put :: s -> State s ()
put s = State $ \_ -> ((), s)
-- mutate the current state
modify :: (s -> s) -> State s ()
modify f = get >>= \x -> put (f x)
Here’s a small example
test :: State Int Int
test =
put 3 >>= \_ ->
modify (+1) >>= \_ ->
get
res = test 0
IO
Finally the biggest, baddest of them all: IO. Not much to say here because IO
is actually not
implemented in Haskell (i.e. it is implemented in the runtime rather than as a library).
You can play with it in a clean interpreter session or in this same session if you
import qualified Prelude
instance Monad IO where
m >>= k = m Prelude.>>= k
return = Prelude.return
main :: IO ()
main =
putStrLn "What is your name:" >>= \_ ->
getLine >>= \name ->
putStrLn name
Acknowledgements
Examples/insights pulled from
What I Wish I Knew When Learning Haskell
All the code
import Prelude hiding ( Monad(..) )
import Data.Either
import qualified Prelude
class Monad m where
-- | Sequentially compose two actions, passing any value produced
-- by the first as an argument to the second.
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= k = Nothing
return = Just
desugared2 :: Maybe Int -> Maybe Int
desugared2 monkeyWrench =
Just 3 >>= \a ->
monkeyWrench >>= \b ->
return $ a + b
instance Monad [] where
m >>= f = concat (map f m)
return x = [x]
m :: [Int]
m = [1, 2, 3, 4]
f :: Int -> [Int]
f = \x -> [1, 0]
demo = m >>= f
instance Monad (Either e) where
return = Right
Right m >>= k = k m
Left e >>= _ = Left e
demo1 = Left "boom" >>= \x -> return (x + 1)
demo2 = Right 100 >>= \x -> Left "no way!"
instance Monad ((->) r) where
return a = (\_ -> a)
m >>= k = \r -> (k (m r)) r
-- just returns the environment
ask :: a -> a
ask = id
-- transforms the env (usually used to access member in record)
asks :: (r -> a) -> (r -> a)
asks f = f
-- Executes a computation in a modified environment.
local
:: (r -> r) -- function to modify env
-> (r -> a) -- reader to run in the modified env
-> (r -> a)
local f m = m . f
-- example
data MyContext = MyContext
{ foo :: String
, bar :: Int
} deriving (Show)
updateContext :: MyContext -> MyContext
updateContext m = m { foo = "foo" }
computation :: MyContext -> (Maybe String)
computation = asks bar >>= \n ->
local updateContext (asks foo) >>= \x ->
if n > 0 then
return (Just x)
else
return Nothing
ex1 :: Maybe String
ex1 = computation $ MyContext "hello" 1
ex2 :: Maybe String
ex2 = computation $ MyContext "haskell" 0
instance Monoid w => Monad ((,) w) where
return a = (mempty, a)
m >>= k =
let (w, a) = m
(w', b) = k a
in (w `mappend` w', b)
tell :: w -> (w, ())
tell w = (w, ())
type MyWriter = ([Int], String)
example :: MyWriter
example = do
tell [1 .. 3]
tell [3 .. 5]
return "foo"
output :: ([Int], String)
output = example
newtype State s a = State (s -> (a,s))
instance Monad (State s) where
return a = State $ \s -> (a, s)
sm >>= k = State $ \s ->
let (State st) = sm
(a, s') = st s
(State st') = k a
in st' s'
get :: State s s
get = State $ \s -> (s, s)
put :: s -> State s ()
put s = State $ \_ -> ((), s)
modify :: (s -> s) -> State s ()
modify f = get >>= \x -> put (f x)
instance Monad IO where
m >>= k = m Prelude.>>= k
return = Prelude.return
main :: IO ()
main =
putStrLn "What is your name:" >>= \_ ->
getLine >>= \name ->
putStrLn name