somesoaccount
somesoaccount

Reputation: 1267

How do i use Debug.Trace.trace in the state monad?

I want to keep track of changes in the state monad. This does not work:

main :: IO ()
main = do
    print $ snd $ execState compute initialState

traceThis :: (Show a) => a -> a
traceThis x = trace ("test: " ++ show x) x

compute :: State ([Row], Integer) String
compute = liftM traceThis $ get >>= \(rs, result) -> put (rs, result + 3) >> return "foo"

Nothing gets printed (except the end result from the print in the main function which has correctly been updated).

Any ideas or alternatives to track state? I want to use this for checking correctness of a project euler solution.

Upvotes: 11

Views: 2281

Answers (3)

Petr
Petr

Reputation: 63359

The problem in your case is that traceThis is never getting evaluated. Haskell is a lazy language, so it only evaluates expressions that are needed. And since you don't evaluate computation result, only the state, it's not necessary to evaluate traceThis inside compute. If you print for example

print $ evalState compute initialState

then the result value of the stateful computation gets evaluated along with calling traceThis.

A better option would be to define a monadic function that forces printing the result value whenever any part of the monadic computation is evaluated:

traceState :: (Show a) => a -> State s a
traceState x = state (\s -> trace ("test: " ++ show x) (x, s))

compute :: State ([Int], Integer) String
compute = get >>= \(rs, result) -> put (rs, result + 3)
              >> return "foo"
              >>= traceState

Update: This can be generalized to an arbitrary monad. The main point is that trace must wrap the monadic computation, not just the value inside, so that it gets evaluated when >>= is evaluated, regardless of whether the value inside is evaluated or not:

traceMonad :: (Show a, Monad m) => a -> m a
traceMonad x = trace ("test: " ++ show x) (return x)

Upvotes: 14

ajd
ajd

Reputation: 1022

When you call execState, you are just asking for the final state, not for the value returned by the compute function. liftM, on the other hand, lifts your traceThis function to an action in the State monad that doesn't touch the state. Thus, due to laziness, traceThis will only be called if you force the value returned by compute to be evaluated. In general, for trace to work properly, you have to be sure that the value that you call it on gets evaluated.

Debug.Trace is generally only suitable for quick debugging - it is not a very powerful logging system and can be difficult to use due to laziness. If you are looking for a way to do this more robustly, you could add another element (perhaps a list of strings) to your state tuple and have the compute function write log messages to that.

Upvotes: 5

Gabriella Gonzalez
Gabriella Gonzalez

Reputation: 35089

Here's an alternative. Use StateT s IO as your monad:

compute :: StateT ([Row], Integer) IO String
compute = do
    (rs, result) <- get
    lift $ putStrLn "result = " ++ show result
    put (rs, result + 3)
    return "foo"

Now you can interleave IO actions anywhere using lift.

To learn more about monad transformers, I recommend you read the excellent introduction: Monad Transformers - Step by Step.

Upvotes: 5

Related Questions