You are on page 1of 9

A newbie in Haskell land

I am a newbie in the Haskell land. I was lost but found some good maps and
discovered there is a tradition in Haskell land : writing a monad tutorial.

There are so many monad tutorials that writing a new one is getting difficult.
And writing a good one if even more difficult. So, I am just going to explain
my own understanding.

The first thing to note is that monads are EASY !!

What's difficult is trying to understand what they have in common because


they can look so different. I have identified three kinds of monads (not
exclusive - a monad can belong to more than one kind):

Monad as control of the sequencing ;


Monad as control of side effects ;
Monad as container
1. Monad as control of the sequencing

In a lazy functional programming language like Haskell, the order of


evaluation does not matter. It does not mean you cannot control the order of
evaluation. It means you can abstract it and build your own sequencing, your
own control.

In imperative languages (like C), you need to extend the language to support
new control statements.

In less elegant functional languages (like LISP) you need to have special
forms which do not follow the normal rules for evaluation.

In Haskell, you "just" build your own control operators. Let's see some
examples:

1.1. Control in IO monad

repeatN 0 a = return ()
repeatN n a = a >> repeatN (n-1) a

test = repeatN 3 $ do
putStrLn "TEST"
And, if you want to pass the loop index to the loop body, you may write:

repeatN 0 a = return ()
repeatN n a = (a n) >> repeatN (n-1) a

test = repeatN 3 $ \i -> do


putStrLn $ "TEST : " ++ (show i)
1.2. Indeterminism monad also known as List monad

Another example of control of the sequencing is the indeterminism monad:

import Control.Monad.List

-- f is a function returning several possible results


f :: Int -> [Int]
f x = [1+x,2*x]

test :: IO ()
test = putStrLn . show $ do
a <- return 5
b <- f a
return b
Here we apply a function f to the value 5. The function f is returning several
possible results.

It is possible to chain indeterminate functions like f:

test2 :: IO ()
test2 = putStrLn . show $ do
a <- return 5
b <- f a
c <- f b
return c
but we do not need to give a name to the intermediate results, so let's write
it like:

test2 :: IO ()
test2 = putStrLn . show $ return 5 >>= f >>= f
The Maybe and Either monads are special cases

2. Monad as control of side effects

2.1. IO Monad

It is the standard example so I won't write about it

2.2. Reader monad

A reader monad is used to maintain an environment.

import Control.Monad.Reader

-- The data type for my environment


data MyState = MyState { vara :: Int
, varb :: Int
}

-- The initial environment


initState = MyState { vara = 10
, varb = 20
}

-- Computation in the initial environment


test = do theVarA <- asks vara
lift . putStrLn $ show theVarA
`runReaderT` initState
We create a Reader monad to have access to the environment defined by
initState. Then in the monad, we can access the fields of initState.

This state is available whenever we need it in the monad and we do not need

to pass it as argument.

runReaderT and lift are explained later. They are not important to understand
this example. You just have to know that the line with lift is used to display a
value and the runReaderT is used to initialize the environment.

Now, we can temporarily change the value of one variable and work in this
modified environment.

-- Increment vara from the environment


incrementVarA :: Int -> MyState -> MyState
incrementVarA x p = p {vara = (vara p) + x}

test = do theVarA <- asks vara


lift . putStrLn $ show theVarA
-- computation in the new modified environment
local (incrementVarA 5) $ do
theVarA <- asks vara
lift . putStrLn $ show theVarA
theVarA <- asks vara
lift . putStrLn $ show theVarA
`runReaderT` initState
We have a side effect since the environment is modified and this change is
visible in a non local way. But this change is nevertheless restricted by the
local function.

The previous examples are in fact using the Reader monad and the IO monad
hence the use of the monad transformer ReaderT and runReaderT.

You may use runReader. With runReader the type of test is no more IO () but
Int:

test = do theVarA <- asks vara


return theVarA
`runReader` initState
So, an equivalent code (with IO) is:

test = putStrLn . show $ do theVarA <- asks vara


return theVarA
`runReader` initState
runReader has type : Reader r a -> r -> a

It is applying a Reader monad to an initial environment (r).

runReaderT is just a bit more complex. It has type: ReaderT r m a -> r -> m a

So, when you're working in ReaderT r IO a, you need to specify if you are
working with values of type ReaderT r IO a or IO a. The lift function is used for
this. Its type is m a -> t m a. So it will transform IO a values to ReaderT r IO a.

A different way to look at this (probably a wrong way) is:

If you have a value v of type a, you use return v to inject it in the ReaderT r IO
a monad.

return v would not work if v was of type IO a since you would get a value of
type ReaderT r IO (IO a).

So, lift is used to inject the value in the monad.

3. Monad as container

In each monad, you have the return function which is injecting an element
into the monad. So, any monad can be seen as a kind of container. For the
List monad it is obvious. Seeing a monad as a container can be very useful.

Assume you want to add an integer to the result of a computation which


could return no result. You may have to do something like that

result = Just 20

test = case result of


Just a -> Just (a + 10)
_ -> Nothing
So, you need to extract the value from the container (if there is something to
extract), apply your function and package the result in the same container.

Or you can just write:

test = (+10) `fmap` result


fmap is a kind of generalization of map. map is lifting a function a -> b to the
container [a] -> [b]

fmap is doing the same for a container m (a monad). So, fmap is transforming
the type a -> b to m a -> m b

4. Deriving monad (you have to use -fglasgow-exts)

In a same code you may have to use different Reader monads even if they
have the same type since they may be for different uses.

You may create a type synonym :

type MyEnvironment a = Reader Int a -- (here the environment is just an Int)


But it would not prevent from mixing two different Reader monads if they
have the same type.

So, you need to create a new type:

newtype MyEnvironment a = MyEnvironment {runMyEnvironment :: Reader


Int a}
Then, you want the same behavior. This is just a reader monad (from a
behavior point of view) like newtype Meter = Meter Int is just a number (from
a behavior point of view).

So, instead of having to write several instance declarations, you just write:

import Control.Monad.Reader
import Control.Monad.Identity

newtype MyEnvironment a = MyEnvironment {runMyEnvironment :: Reader


Int a}
deriving (Monad, MonadReader Int)
Then you create an environment . It is just a Reader monad contained in your
new type

r :: MyEnvironment Int
r = do
r <- MyEnvironment $ ask -- This is packaging the result of ask
-- in MyEnvironment. Hence the work is done
return r

-- in the MyEnvironment monad and not in a


-- simple Reader monad r is an Int but
-- return r is a MyEnvironment Int and not
-- a Reader Int Int

Then, you extract the reader monad and apply it to the initial state

test = putStrLn . show $ (runMyEnvironment r) `runReader` 4


5. What's common ?

What do the previous monads have in common ? Nothing ! Or not a lot.


Indeed, being a monad is a very general concept and focusing on the part
they have in common (return, >>=) is not the interesting part nor the
difficult one. What is interesting is how different they are : a Reader monad is
providing ask and local functions ; an IO monad is providing putStrLn etc...

Each monad has its own personality. Of course, >>= will not be the same in
each monad but from a user point of view, it will respect the same monadic
laws :

return a >>= k == k a

-- return is a "neutral element" on left

m >>= return == m

-- return is a "neutral element" on right

m >>= (\x -> k x >>= h) == (m >>= k) >>= h -- a kind of associativity of


>>=
The only things shared by all monads : the monadic laws.

You might also like