Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

haskell

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.

like image 619
somesoaccount Avatar asked Aug 07 '12 12:08

somesoaccount


3 Answers

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)
like image 126
Petr Avatar answered Oct 16 '22 04:10

Petr


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.

like image 5
Gabriella Gonzalez Avatar answered Oct 16 '22 05:10

Gabriella Gonzalez


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.

like image 5
ajd Avatar answered Oct 16 '22 06:10

ajd