使用 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 Void
或 IO a
类型,但这是一个吹毛求疵。)
我想知道为什么这个 "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 Void
或 IO a
类型,但这是一个吹毛求疵。)