Take the 2-minute tour ×

So I was building a State Monad and ran into some problems with its lazy nature that is making it hard for me to debug.

My State monad operates by getting in a list of values, pushing them one by one on to part of the state, then I analyze what values are on the state after each one to produce the other part of the state.

I came up with this simple example to illustrate why it is hard to debug.

module Main where

import Control.Monad.State
import Debug.Trace

runSim :: [Int] -> State String String
runSim [] = return =<< get
runSim (cur:xs) = do
    lst <- get
    let val =  (show $ 2*cur)
    put $ trace ((show $ length lst) ++ " " ++ (show cur)) ((val) ++ "," ++ lst)
    runSim xs

main :: IO ()
main = print $ evalState (runSim [1..10]) ""

The output of this is:

0 1
2 2
4 3
6 4
8 5
11 6
14 7
17 8
20 9
23 10

However if I change my trace line to this:

put $ trace ((show cur)) ((val) ++ "," ++ lst)

The output is reversed:


But the end result is the same. Is there a better way to handle the laziness of the State Monad in debugging so it is more naturally sequential?

share|improve this question
Not only that, but ghc and ghci produce different outputs, for both variants of trace line. –  n.m. Oct 29 '12 at 19:04
Well, there's always the strict state monad. –  Daniel Wagner Oct 29 '12 at 19:19

2 Answers 2

up vote 8 down vote accepted

The problem is that the trace calls are only evaluated at the end.

The computation builds the state like (taking a list of only two elements for brevity)

runSim [1, 2] "" ~> ( (), state1@(trace (output 1 "") (logString 1 "")))
~> runSim [2] ( (), trace (output 2 state1) (logString2 state1))

so in the final state, the trace for the last pushed list element is the outermost.

Now in the second case, where

output i _ = show i

the tracing output doesn't depend on what happened earlier, so the trace pushed last is run first etc.

But in the first case, where

output i state = show (length state) ++ " " ++ show i

the tracing output depends on the state, so the state has to be evaluated before the tracing output can be printed. But state is a call to the previously pushed trace, so that trace needs to run first, etc. And so the data-dependencies in the tracing output ensure the traces are run in the order of pushing.

To ensure that the traces are run in that order without data-dependencies, you must pull the trace call out of the put, or force evaluation of the put state,



trace ((show $ length lst) ++ " " ++ (show cur)) $ put ((val) ++ "," ++ lst)
share|improve this answer
  1. It's a monad law that m >>= return === m. Therefore, you can leave the return =<< away in runSim [].
  2. Your end result is the wrong way round because you prepend your current value to the state, but what you want is appending. Change ((val) ++ "," ++ lst) to (lst ++ "," ++ val), and your output is what you expected: ",2,4,6,8,10,12,14,16,18,20". (You can worry about the leading comma later.)
  3. The state monad is for reading and writing states. In your case, all you want to do is to write, and that's what the Writer monad is for:

    import Control.Monad.Writer
    import Data.List (intercalate)
    main = putStrLn . intercalate ", " . map show $ execWriter (runSim [1..10])
    runSim :: [Int] -> Writer [Int] ()
    runSim [] = return ()
    runSim (x:xs) = tell [2*x] >> runSim xs
    ==> "2, 4, 6, 8, 10, 12, 14, 16, 18, 20"

    (Note that Writer [] can be very ineffective when the written list gets longer. Use DList if you're doing a lot of writing.)

  4. A general note: Because Haskell mostly consists of small functions that combine well, it's often very easy to reason about what a single function does. Your runSim gets the current state and calculates the double of the list head. After that, it does some trace stuff, and then puts val ++ "," ++ lst back as the new state, before recursing as a final step. The great thing about Haskell is that that's the only thing this function can do: Take state, multiply, put state. If you understand one iteration of this, you'll understand all iterations. What I mean to say with that is that - even in your case it wasn't a problem with laziness - laziness doesn't influence the result of your program (and in fact can't, unless you've got bottoms in there maybe). It doesn't matter what trace gives you in an intermediate step, if GHC decides to walk the list backwards with even numbers first and then forward with the odd ones, the result is still going to be the same. trace however contains evil magic, so it can be confusing. I would use trace more as a jackhammer way of looking inside your program, and not to reason about how it does what it does.
share|improve this answer
Thanks for the response, but this isn't the code that I am debugging. This is merely example code to show how the output flips depending on what your trace messages are. –  DantheMan Oct 29 '12 at 17:59

Your Answer


