问题
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