9

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

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"

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

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

4

3 回答 3

12

您的问题traceThis是永远不会被评估。Haskell 是一种惰性语言,因此它只计算需要的表达式。而且由于您不评估计算结果,只评估状态,因此没有必要评估traceThisinside 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

更新:这可以推广到任意单子。要点是trace必须包装一元计算,而不仅仅是内部的值,以便在评估时>>=对其进行评估,无论内部的值是否被评估:

traceMonad :: (Show a, Monad m) => a -> m a
traceMonad x = trace ("test: " ++ show x) (return x)
于 2012-08-07T12:49:22.783 回答
5

这是一个替代方案。StateT s IO用作你的单子:

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

于 2012-08-07T12:40:45.057 回答
5

当您调用 时execState,您只是询问最终状态,而不是compute函数返回的值。liftM,另一方面,将您的traceThis功能提升到State不触及状态的单子中的动作。因此,由于懒惰,只有在强制计算返回traceThis的值时才会调用。一般来说,为了正常工作,您必须确保您调用它的值得到评估。computetrace

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

于 2012-08-07T12:44:57.480 回答