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

百般思念 提交于 2021-02-05 05:54:12

问题


I want to know why this "debug message 1" doesn't get printed in this snippet:

import Debug.Trace

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

The second "debug message 2" is printed out, but not "debug message 1". It seems that both expressions are the same.

I tried binding the "debug message 1" to a variable, and then using that variable in another place, it did in fact trigger the evaluation and print "debug message 1", but I still don't understand why this happens.

If I flip the order of the statements, it is still the same result:

import Debug.Trace

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

"debug message 1" is never printed (using runhaskell).


回答1:


My guess would be because of "lazy evaluation".

Note that you don't return anything. In other words, the "return" is not been queried yet (well there is no return), nor is it useful. Inside the return statement, you are not in a "monadic" context. So there is no reason to evaluate it, you simply pass the "call-tree" as the result.

In other words, it remains on the "call-tree" until someone wants to pick it up.

For the second case it is trivial that the trace will be called. The monad is executed until it reaches a "return", and before that return is reached, all necessary actions are taken, including executing debug info if needed.

Example in 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

Same for runhaskell. If you write these two programs:

program1.hs:

import Debug.Trace

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

program2.hs:

import Debug.Trace

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

Then the console reads:

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

If you however write an IO Bool (thus with return value) and you later use that value, the trace will be executed, for instance:

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

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

This will result in:

$ runhaskell program3.hs
foo
Hello
True
$ 

If you however omit the print b and put return () instead, Haskell has no interest in the what is returned and thus doesn't print the trace:

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

The result is:

$ runhaskell program4.hs
foo
$ 



回答2:


There is no special magic about do notation.

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

is just the same as

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

By one of the monad identity laws, return a >>= f = f a, so

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

The function ignores its argument, so the argument is not evaluated; the expression reduces to

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

The first message is entirely gone, and you can see that the remaining trace is now the outermost application that must be reduced to evaluate main, so this message will be printed.

When you flipped the order, you got

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

This is the same as

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

The situation here is a bit more complicated. The first trace (message 2) is forced, because >>= for IO is strict in its left operand. Then return () is executed, doing nothing. Its value is ignored, and the final action, return (trace "debug message 1" ()) is executed. This also does nothing (return never does anything interesting). Since the end of the main action is the end of the program, this return value is never inspected, and thus never forced, so it is not evaluated. Some people think that main should be required to have the type IO () to emphasize that its return value is never used. (I believe they are wrong about this, because programs that run forever should really have type IO Void or IO a, but that's a nitpick.)



来源:https://stackoverflow.com/questions/28673546/lazy-evaluation-in-haskells-do-notation-using-the-trace-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!