我如何在状态monad中使用Debug.Trace.trace?

时间:2012-08-07 12:22:08

标签: haskell

我想跟踪状态monad的变化。这不起作用:

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"

没有打印任何内容(除了主函数中已正确更新的打印的最终结果)。

跟踪状态的任何想法或替代方案?我想用它来检查项目euler解决方案的正确性。

3 个答案:

答案 0 :(得分:11)

您的问题是traceThis永远不会被评估。 Haskell是一种惰性语言,因此它只评估所需的表达式。由于您不评估计算结果,只评估状态,因此无需评估traceThis内的compute。如果您打印例如

print $ evalState compute initialState

然后计算有状态计算的结果值以及调用traceThis

更好的选择是定义一个monadic函数,在评估monadic计算的任何部分时强制打印结果值:

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

更新:这可以推广到任意monad。重点是trace必须包装monadic计算,而不仅仅是内部的值,以便在评估>>=时评估它,无论是否评估内部值:

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

答案 1 :(得分:4)

这是另一种选择。使用StateT s IO作为您的monad:

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

现在,您可以使用IO在任何地方交错lift个操作。

要了解有关monad变换器的更多信息,建议您阅读优秀的介绍:Monad Transformers - Step by Step

答案 2 :(得分:4)

当您致电execState时,您只是要求最终状态,而不是compute函数返回的值。另一方面,liftM会将traceThis函数提升为State monad中不会触及状态的动作。因此,由于懒惰,只有在强制traceThis返回的被评估时才会调用compute。通常,要使trace正常工作,您必须确保对其调用的值进行评估。

Debug.Trace通常仅适用于快速调试 - 它不是一个非常强大的日志系统,并且由于懒惰而难以使用。如果您正在寻找一种更健壮的方法,您可以向状态元组添加另一个元素(可能是字符串列表),并让compute函数向其写入日志消息。