使用 trace 函数在 Haskell 的 do 符号中进行惰性求值

Lazy evaluation in Haskell's do notation using the trace function

我想知道为什么这个 "debug message 1" 没有打印在这个片段中:

import Debug.Trace

main = do
    return (trace "debug message 1" ())
    trace "debug message 2" (return ())

打印出第二个 "debug message 2",但没有打印出 "debug message 1"。看来这两个表达是一样的

我尝试将 "debug message 1" 绑定到一个变量,然后在另一个地方使用该变量,它确实触发了评估并打印 "debug message 1",但我仍然不明白为什么发生这种情况。

如果我翻转语句的顺序,结果还是一样:

import Debug.Trace

main = do
    trace "debug message 2" (return ())
    return (trace "debug message 1" ())

"debug message 1" 永远不会打印(使用 runhaskell)。

我猜是因为 "lazy evaluation"。

请注意,您没有 return 任何东西。换句话说,"return" 还没有被查询(嗯,没有 return),也没有用。在 return 语句中,您不在 "monadic" 上下文中。所以没有理由评估它,你只需将 "call-tree" 作为结果传递。

换句话说,它会一直保留在“调用树”中,直到有人想要拿起它。

对于第二种情况,调用 trace 是微不足道的。 monad 一直执行到到达“return”,在到达 return 之前,将执行所有必要的操作,包括在需要时执行调试信息。

ghci中的示例:

$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> import Debug.Trace
Prelude Debug.Trace> do return (trace "debug message 1" ())
Prelude Debug.Trace> do trace "debug message 2" (return ())
debug message 2

runhaskell 相同。如果你写这两个程序:

program1.hs:

import Debug.Trace

main = do return (trace "debug message 1" ())

program2.hs:

import Debug.Trace

main = do
    trace "debug message 2" (return ())

然后控制台显示:

$ runhaskell program1.hs
$ runhaskell program2.hs
debug message 2
$ 

但是,如果您写了一个 IO Bool(因此具有 return 值)并且稍后使用该值,则将执行跟踪,例如:

testFun :: IO Bool
testFun = do
    trace "foo" $ return $ trace "Hello" True

main :: IO ()
main = do
    b <- testFun
    print b

这将导致:

$ runhaskell program3.hs
foo
Hello
True
$ 

但是,如果您省略 print b 并用 return () 代替,Haskell 对 return 编辑的内容不感兴趣,因此不会打印跟踪:

testFun :: IO Bool
testFun = do
    trace "foo" $ return $ trace "Hello" True

main :: IO ()
main = do
    b <- testFun
    return ()   --we ran `testFun` but are not interested in the result

结果是:

$ runhaskell program4.hs
foo
$ 

do 符号没有什么特别的魔法。

main = do
    return (trace "debug message 1" ())
    trace "debug message 2" (return ())

相同
main = return (trace "debug message 1" ()) >>=
        \_ -> trace "debug message 2" (return ())

根据 monad 身份法则之一,return a >>= f = f a,所以

main = (\_ -> trace "debug message 2" (return ()))
         (trace "debug message 1" ())

该函数忽略了它的参数,因此不计算参数;表达式减少到

main = trace "debug message 2" (return ())

第一条消息完全没有了,你可以看到剩下的trace现在是最外层的应用程序,必须缩减以评估main,所以会打印这条消息。

当你翻转订单时,你得到了

main = do
    trace "debug message 2" (return ())
    return (trace "debug message 1" ())

这与

相同
main = trace "debug message 2" (return ()) >>=
         (\_ -> return (trace "debug message 1" ()))

这里的情况有点复杂。第一个 trace(消息 2)是强制的,因为 >>= for IO 在其左操作数中是严格的。然后 return () 被执行,什么都不做。它的值将被忽略,并执行最终操作 return (trace "debug message 1" ())。这也什么都不做(return never 做任何有趣的事情)。由于 main 操作的结束是程序的结束,因此从不检查此 return 值,因此从不强制执行,因此不对其求值。有些人认为应该要求 main 具有类型 IO () 以强调它的 return 值永远不会被使用。 (我相信他们对此是错误的,因为永远 运行 的程序确实应该具有 IO VoidIO a 类型,但这是一个吹毛求疵。)