Professional Documents
Culture Documents
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.
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:
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
import Control.Monad.List
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.
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.1. IO Monad
import Control.Monad.Reader
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.
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:
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.
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).
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.
result = Just 20
fmap is doing the same for a container m (a monad). So, fmap is transforming
the type a -> b to m a -> m b
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.
So, instead of having to write several instance declarations, you just write:
import Control.Monad.Reader
import Control.Monad.Identity
r :: MyEnvironment Int
r = do
r <- MyEnvironment $ ask -- This is packaging the result of ask
-- in MyEnvironment. Hence the work is done
return r
Then, you extract the reader monad and apply it to the initial state
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
m >>= return == m