我想跟踪状态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解决方案的正确性。
答案 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
函数向其写入日志消息。