【问题标题】:Lazy evaluation in Haskell's do notation using the trace function使用跟踪函数在 Haskell 的 do 表示法中进行惰性求值
【发布时间】:2023-03-09 06:20:01
【问题描述】:

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

import Debug.Trace

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

第二个“debug message 2”被打印出来,但不是“debug message 1”。看来这两种表达方式是一样的。

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

如果我把语句的顺序颠倒一下,结果还是一样的:

import Debug.Trace

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

“调试消息 1”从不打印(使用 runhaskell)。

【问题讨论】:

    标签: haskell lazy-evaluation


    【解决方案1】:

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

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

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

    根据单子身份法则之一,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 从不 做任何有趣的事情)。由于main 动作的结束是程序的结束,所以这个返回值永远不会被检查,因此永远不会被强制,所以它不会被评估。有些人认为应该要求main 具有IO () 类型,以强调它的返回值从不使用。 (我认为他们对此是错误的,因为永远运行的程序确实应该具有 IO VoidIO a 类型,但这是一个挑剔的问题。)

    【讨论】:

    • 您可以添加对haskell.org/onlinereport/modules.html 的引用,其中表示“当程序执行时,计算 main 被执行,并且其结果(类型 t)被丢弃。”
    • 不错的答案。那么如果不是(由你的结论暗示)IO()的主要类型是什么?
    • @CMCDragonkai,对于任何a,它都可以是IO a。不管是什么,我们都不会要求这个结果。
    【解决方案2】:

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

    请注意,您不会返回任何内容。换句话说,“return”还没有被查询(当然没有return),也没有用。在return 语句中,您不在“单子”上下文中。所以没有理由评估它,你只需传递“调用树”作为结果。

    换句话说,它一直在“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
    $ 
    

    【讨论】:

    • 如果我翻转订单,同样的事情也会发生。 main = do trace "debug message 2" (return ()); return (trace "debug message 1" ())
    • @CMCDragonkai:如果我在ghci 中运行它,第二次它会打印调试消息。在此上下文中没有打印它的原因是因为它放在了 return 语句之后。但是,如果您执行do trace "debug message 2" (return ()),它将打印调试消息。
    • runhaskell运行这个。
    • 需要明确的是,当我使用runhaskell 运行它时,无论主语句的顺序如何,都不会打印“调试消息 1”。
    • @CMCDragonkai:ghci 或多或少等同于runhaskell,但具有交互性。如您所见,打印了第二条调试消息。但显然,您不能将它放在return 语句之后。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-06
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2014-11-09
    • 1970-01-01
    相关资源
    最近更新 更多