ghci 中的 `trace` 函数不会在第二次调用时打印

Chu*_*uan 2 haskell ghci

问题:

trace 函数在第二次调用时不起作用,但只有在将包含 trace 的函数加载到 ghci 时才会发生这种情况。

题

  1. 为什么会发生这种情况?
  2. 如何加载 Haskel 模块并仍然具有预期的行为?

意外行为:

我有一个文件叫 test.hs

import Debug.Trace (trace)
main = trace "test" return "main is called"
Run Code Online (Sandbox Code Playgroud)

然后我在 ghci 中输入以下内容。关于第二次调用的注意事项main trace没有打印“test”

Prelude> :l test
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, one module loaded.
*Main> main
test
"main is called"
*Main> main      -- No output from trace?
"main is called"
Run Code Online (Sandbox Code Playgroud)

预期行为

但是,如果我在 ghci 中键入 main 函数,则会得到预期的行为

Prelude> import Debug.Trace (trace)
Prelude Debug.Trace> main = trace "test" return "main is called" 
Prelude Debug.Trace> main
test
"main is called"
Prelude Debug.Trace> main -- Output from trace
test
"main is called"
Prelude Debug.Trace> 
Run Code Online (Sandbox Code Playgroud)

更新

@Robin Zigmond 建议,我尝试了不同的括号但没有成功

main = trace "test" (return "main is called")
main = return(trace "test" "main is called") -- Joins the outputs into one string
Run Code Online (Sandbox Code Playgroud)

dan*_*iaz 9

首先,大致了解 Haskell 如何评估事物。考虑这个表达式

(\x -> x * x) (2 + 2)
Run Code Online (Sandbox Code Playgroud)

Haskell 很懒;函数在调用之前不会评估它们的参数,所以这样做的一种天真的方法是:

(2 + 2) * (2 + 2)
Run Code Online (Sandbox Code Playgroud)

但这会使工作加倍!

相反,所做的事情是这样的:

    *
   /  \
   \  /
   (2+2)
Run Code Online (Sandbox Code Playgroud)

也就是说,运行时记住它2 + 2来自一个地方,当它被评估时,结果在表达式的其他部分被重用:

    *
   /  \
   \  /
     4
Run Code Online (Sandbox Code Playgroud)

进而

     16
Run Code Online (Sandbox Code Playgroud)

该trace函数包装一个表达式,并且只在表达式第一次“弹出”时打印消息。如果再次请求结果,它不会再次打印消息:

ghci> (\x -> x * x) (trace "pop!" (2 + 2))
pop!
16
Run Code Online (Sandbox Code Playgroud)

谜题的下一部分是IO行动。在 Haskell 中,像IO动作这样类似语句的东西实际上和其他任何东西一样都是值。它们可以作为参数传递给函数,也可以从函数中返回。只是运行时将它们解释为在现实世界中执行的有效操作的描述。

因此,IO something必须在执行之前(在“与世界交互”的意义上)评估类型为 的表达式(在纯粹的、懒惰的意义上)。例如:

(\x -> x >> x) (trace "test" (putStrLn "foo"))
Run Code Online (Sandbox Code Playgroud)

变成

        (>>)
       /  \
       \  /
(trace "test" (putStrLn "foo"))
Run Code Online (Sandbox Code Playgroud)

在这里,我们弹出表达式并打印一次“test”

        (>>)
       /  \
       \  /
    putStrLn "foo"
Run Code Online (Sandbox Code Playgroud)

运行时看到类似

putStrLn "foo", then putStrLn "foo" again
Run Code Online (Sandbox Code Playgroud)

实际上,您写的内容略有不同:(trace "test" return) "main is called". 在您的代码trace中包装return函数,而不是结果IO String值。但是效果是类似的,函数在 Haskell 中也是值。必须在调用函数之前评估类型为函数的表达式。

此外,在您的情况下,发生的情况是该main操作被评估一次,并执行多次。

请注意,trace的效果(在控制台上打印内容)与正常的IO操作流程是带外的。它们是根据惰性求值的变幻莫测而发生的。这是一个不应用于“实际”工作的调试功能。


更新:我忘了回答这部分问题:为什么trace在 ghci 实验中会出现您期望的行为?

trace "test" (return "foo")是一个多态值,它实际上并没有指定一个具体的 monad (try :t return "foo")。ghci 的默认规则会IO在执行之前将其实例化。这种多态性使得 ghci 每次输入时都会重新计算值main。如果您提供显式类型注释,例如main = (trace "test" (return "foo")) :: IO String.