How to debug inside state monad

Hey! I am pretty new to Haskell and am trying to figure out how to do simple “println debugging” inside do-notation for a state monad. For example, I would like to print this variable vistedCasesAndDeps: https://github.com/leblowl/Carp/blob/7e733fbb495f3f69b2d1b7af4ca89a989255f3b5/src/Memory.hs#L352 … but it never prints. And I am running into this issue over and over again. Sometimes the trace prints and sometimes it doesn’t. The problem I think I am running into is Haskell’s laziness. It doesn’t want to print certain things, because it doesn’t need to? Is there anyway to force evaluation of trace calls? Are there any other tips for debugging in Haskell? Thanks!

In a monadic context you can use traceM

1 Like

Often in a do block I’ll say:

do
  x
  trace "something" $ pure ()
  y

Yes I tried traceM, but still doesn’t always print.

Tried with traceM and @sclv your suggestion: https://github.com/leblowl/Carp/blob/145e466478c17c58ab94233857e47b3f897d794b/src/Memory.hs#L352 but still nothing, and I don’t really understand why or how to work around it.

This may be due to your use of Control.Monad.State which picks the lazy state by default.

I personally always attach my trace to a piece of code of which I know it will be executed, that should work even if you use lazy state. In your code I would attach it to the case that follows it by replacing the (return ()) with $ and indenting the whole case expression.

You could also try switching to Control.Monad.State.Strict.

If none of that works then I would start questioning if that code is really executed at all.

1 Like

traceM is the same as @sclv’s suggestion:

traceM :: Applicative f => String -> f ()
traceM string = trace string $ pure ()

traceM works in both the lazy and strict State monads. If nothing is being printed then I strongly suspect that @jaror is correct and that this code is not being executed at all.

> import Control.Monad.State.Lazy as L
> L.runState (do { traceM "Hello"; pure () }) ()
((),Hello
())
1 Like

Thanks @jaror ! I didn’t know that you could use the entire case expression as the return value of trace. If you don’t mind, what does the $ have to do with that? I thought that was used for function application in certain situations where operator precedence is important. And I thought it was an infix operator. Maybe this has to do with currying?

I haven’t tried Control.Monad.State.Strict yet, but another friend of mine gave me some advice with the laziness of the State monad, and how the (return ()) is being ignored. I guess multiple subsequent calls to trace, each setting the current state to (), can be ignored unless there is a get mixed in which forces their evaluation. Because if the current state is already (), setting it to () again would have no effect and the state monad tries to be smart and skip any unnecessary work. Anyways, by adding a get after the second trace, I guess I can force the trace to be evaluated. So I added a new line MemState _ _ _ <- get after line 352 and that worked as well.

Ah, this is interesting! Looks like the lazy State monad only evaluates back to the most recent put:

> L.runState (do { traceM "Hello"; L.put (); pure () }) ()
((),())
> S.runState (do { traceM "Hello"; S.put (); pure () }) ()
Hello
((),())
2 Likes

Normally you would have to write

trace (...) 
  (case .. of
     ... -> ...
     ... -> ...)

That means that trace is applied to (...) as first argument and then to the (case ...) part as second argument, and trace always prints the string in its first argument and returns its second argument, so this will print the part from the (...) and it will then return the result of the case expression.

With $ you can write:

trace (...) $
  case .. of
     ... -> ...
     ... -> ...

This avoid having to write the parentheses around the case expression.

The $ is not the second argument to trace, but trace (...) is the first argument of $ and the case ... is the second argument of $. And $ just applies its first argument to its second argument. Writing trace (...) does only make sense due to currying, applying the trace function to a single argument returns a function that still takes one argument.

1 Like

Ah, this is interesting! Looks like the lazy State monad only evaluates back to the most recent put:

In fact, it lets you execute this “infinite” computation just fine:

ghci> let foo = foo *> put 3 in runState foo 0
((),3)

The “lazy” versions of the State and Writer monads are almost never what you want.

One common misconception is that they are lazy in the state/accumulator. Well, they are, but the strict versions are lazy in the accumulator, too!

Their particularity is that they are lazy in the spine of the computation.

3 Likes