### Grok Haskell Monad Transformers

Another literate Haskell post:

I've tried a few times to read various documents on the web about

Monad Transformers in Haskell. I think that in almost every case the

authors are trying to show how clever they are rather than explaining

their use. If I had just seen the simplest possible examples of Monad

Transformers at work I would have been able to figure out what was going

on and that would have given me enough information to bootstrap myself

into Monad Transforming enlightenment.

So to save other people the trouble I went through I'm providing you

with examples of Monad Transformers at work. I'm not even going to

explain in detail how they work, they're close to self-evident once the

main features are pointed out.

> import Control.Monad.State

> import Control.Monad.Identity

Firstly here are two examples of the use of the State Monad. This

code isn't intended as a tutorial on the State Monad so I won't

explain how they work.

> test1 = do

> a <- get

> modify (+1)

> b <- get

> return (a,b)

> test2 = do

> a <- get

> modify (++"1")

> b <- get

> return (a,b)

> go1 = evalState test1 0

> go2 = evalState test2 "0"

Note how evalState 'unwraps' the State Monad and gives you back the

answer at the end.

So the question is: how do I combine both of these states into one

so that I can update or read either the integer or the string state

at will? I could cheat and make a new state of type (Int,String)

but that isn't a general solution.

The idea is that you use a Monad Transformer. A Monad Transformer

if like a layer of onion peel. You start with the Identity monad

and then use Monad Transformers to add layers of functionality.

So to get a two-state monad you take the Identity and add two

layers of stateness to it. To get the answer at the end you need

to unwrap the State layers and then unwrap the Identity layer too.

When you're inside your 'do' expression you need a way to choose

which Monad you're talking to. Ordinary Monad commands will

talk to the outermost layer and you can use 'lift' to send your

commands inwards by one layer.

The following should now be self-evident:

> test3 = do

> modify (+ 1)

> lift $ modify (++ "1")

> a <- get

> b <- lift get

> return (a,b)

> go3 = runIdentity $ evalStateT (evalStateT test3 0) "0"

Note that we use evalStateT to unwrap the State layer instead

of evalState. evalStateT is what you use to unwrap one layer

of the Monad. evalState is what you use when your entire Monad

is just the State monad. When State is a layer, rather than

the whole thing, it's called StateT. (Try :type go3 in ghci.)

What if you want to combine IO and State? For various reasons

the IO monad must form the innermost core of your onion so

there's no need to wrap an IO layer around the Identity,

you can just start with IO. And when you unwrap you don't

need to unwrap the IO layer because the Haskell compiler does

that for you. (Now you can see why you can't layer IO, you're

not supposed to unwrap IO as it's the job of the code that

calls main at the top level to do that.)

So here we go:

> test5 = do

> modify (+ 1)

> a <- get

> lift (print a)

> modify (+ 1)

> b <- get

> lift (print b)

> go5 = evalStateT test5 0

At this point you might be suspicious that IO is being handled

slightly differently from State. So here's a StateT layer

wrapped around State:

> test7 = do

> modify (+ 1)

> lift $ modify (++ "1")

> a <- get

> b <- lift get

> return (a,b)

> go7 = evalState (evalStateT test7 0) "0"

You can probably safely ignore my comments and crib the code above

directly for your own use.

And credit where credit's due: I found the following link to be

more helpful than any other in trying to figure this stuff out

http://www.cs.nott.ac.uk/~nhn/MGS2006/LectureNotes/lecture03-9up.pdf

(But I still felt that this link was obfuscating.)

You may now be wondering where test4 and test6 are. So am I.

Update: I just figured out that you don't always need to use the

'lift' operation. Some of the Monad Transformers have been defined

so that the commands will automatically be passed into the inner

Monad (and further) if needed. I think it's slightly confusing to

write code this way, however.

## 15 Comments:

Pete,

I was going to post this very example on the Haskell Wiki but I had trouble figuring out how to do this. I'm happy for you to do it. If you want, add a link back to here.

If I remember I'll write an example that doesn't use 'lift' and post it here. Otherwise search for "Looking for basic state transformer example" on USENET where I first posted an example. (The title of the USENET post is wrong, I meant to say "monad" but said "state" by accident.)

Thanks. Great post.

Thanks. I found this quite useful.

I

STILLcan't think of one real-world reason why I'd want to use one. I just don't get it (yet).So, if I wanted two kinds of state, one readable and one readable and writable, I could wrap a StateT in a ReaderT? Or vice versa?

Tim,

Exactly. Except that if you just have two types then one can be an ordinary monad, and the other can be a monad transformer. Or you can use three levels where both the reader and the writer are monad transformers and they are applied to the identity monad. (I do the former in this post.)

Very helpful post, thanks!

One thing - did you mean to say "Try :type test3 in ghci." rather than :type go3 ?

The latter just yields:

go3 :: (Integer, [Char])

This post is really great. Thanks!

Hmm, do you really need to wrap this around Identity? Can't you just stack StateT on top of State to just get sort of a DoubleState monad?

chessguy,

It's up to you. If you're bothered by the lack of symmetry from having a StateT and a State then you can use the Identitiy monad.

For those wondering why you would want to use this: as an example, I'm trying to implement Ruby Quiz 19 (Yahtzee). How am I supposed to set up an interactive loop with the user and save the current score and random state (for die rolls) throughout the loop? You can't do IO () stuff like putStrLn to show dice rolls while using State (Score,StdGen) x because your return type is no longer IO (). But with something like the test5 example, it's now entirely possible. A terrible workaround would've been to use IO () to read/write the state to a file... Now I can StateT (Score,StdGen) IO () and all is solved.

Yeah, I'm going to have to agree with sigfpe... it's really just up to you how you want to do it. Sometimes multiple solutions stink!

Can not agree any more with your comments on those egghead's explanation on Monad Transformers. Thanks a lot. Really what I want to know.

Can not agree more with your comments on those fake tutorials. This is just what I want to know. After using that for few weeks, I will begin to understand what Transformer really is.

Here's another nice and thorough and typeset :-) tutorial, posted shortly after sigfpe's original:

http://www.grabmueller.de/martin/www/pub/Transformers.en.html

Post a Comment

<< Home