使用跟踪函数在Haskell中进行惰性求值

时间:2015-02-23 12:11:58

标签: haskell lazy-evaluation

我想知道为什么这个“调试消息1”没有打印在这个片段中:

import Debug.Trace

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

打印出第二个“调试消息2”,但不打印“调试消息1”。似乎两个表达都是一样的。

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

如果我翻转语句的顺序,它仍然是相同的结果:

import Debug.Trace

main = do
    trace "debug message 2" (return ())
    return (trace "debug message 1" ())
永远不会打印

“调试消息1”(使用runhaskell)。

2 个答案:

答案 0 :(得分:7)

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),因为>>=的{​​{1}}在其左操作数中是严格的。然后执行IO,什么都不做。忽略其值,并执行最终操作return ()。这也没有做任何事情(return (trace "debug message 1" ()) 从不做任何有趣的事情)。由于return操作的结束是程序的结束,因此永远不会检查此返回值,因此从不强制,因此不会对其进行评估。有些人认为main应该要求main类型强调它的返回值从未使用过。 (我认为他们错了,因为永远运行的程序应该真的有IO ()IO Void类型,但那是一个挑剔。)

答案 1 :(得分:3)

我的猜测是因为"懒惰评估"。

请注意,您不会返回任何内容。换句话说,"返回"尚未被查询(没有回复),也没有用。在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(因此返回值)并稍后使用该值,则将执行跟踪,例如:

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对返回的内容不感兴趣,因此不会打印跟踪:

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
$