使用GHC 8.0.2版程序:
import Debug.Trace
f=trace("f was called")$(+1)
main = do
print $ f 1
print $ f 2
输出:
f was called
2
3
这是预期的行为吗?如果是,为什么?我希望字符串f was called
可以打印两次,一次在2
之前,一次在3
之前。
TIO上的结果相同:Try it online!
修改 但是这个程序:
import Debug.Trace
f n=trace("f was called:"++show n)$n+1
main = do
print $ f 1
print $ f 2
输出:
f was called:1
2
f was called:2
3
我怀疑这些行为与懒惰有关,但我的问题仍然存在:这是预期的行为,如果是,为什么?
Hackage断言:
跟踪功能输出首先给出的跟踪消息 在返回第二个参数作为结果之前,参数。
我在第一个例子中没有看到它。
编辑2 基于@amalloy评论的第三个例子:
import Debug.Trace
f n=trace "f was called"$n+1
main = do
print $ f 1
print $ f 2
输出:
f was called
2
f was called
3
答案 0 :(得分:4)
在定义f
时打印您的跟踪,而不是在调用时打印。如果您希望跟踪作为调用的一部分发生,则应确保在收到参数之前不对其进行评估:
f x = trace "f was called" $ x + 1
另外,当我运行你的TIO时,我根本看不到痕迹。 trace
并不是一种真正可靠的打印方式,因为它会欺骗构建语言的IO模型。评估顺序中最微妙的变化可能会扰乱它。当然,对于调试,您可以使用它,但即使这个简单的示例也证明它不能保证有用。
在编辑中,引用trace
的文档:
跟踪功能输出首先给出的跟踪消息 在返回第二个参数作为结果之前,参数。
事实上,这正是你的计划中发生的事情!定义f
时,
trace "f was called" $ (+ 1)
需要进行评估。首先," f被称为"打印出来。然后,trace
评估并返回(+ 1)
。这是trace
表达式的最终值,因此(+ 1)
是f
定义的内容。 trace
消失了,看?
答案 1 :(得分:2)
这确实是懒惰的结果。
懒惰意味着仅仅定义一个值并不意味着它将被评估;只有在某些事情需要时才会发生。如果不需要,那么实际产生它的代码就不会做任何事情"。如果需要特定值 ,则运行代码,但仅在第一次需要时运行;如果对同一个值有其他引用并再次使用,那么这些用法将直接使用第一次生成的值。
你必须记住每个意义上的功能都是值;适用于普通值的所有内容也适用于函数。因此,您对f
的定义只是为值编写表达式,表达式的评估将推迟到实际需要f
的值,并且因为它需要两次表达式计算的值(函数)将被保存并第二次重用。
让我们更详细地看一下:
f=trace("f was called")$(+1)
您使用简单的等式定义值f
(不使用任何语法糖来在等式的左侧写入参数,或通过多个等式提供个案)。因此,我们可以简单地将右侧作为定义值f
的单个表达式。只是定义它什么都不做,它就坐在那里直到你打电话:
print $ f 1
现在print需要对其参数进行求值,因此这会强制表达式f 1
。但是,如果没有先强制f
,我们就无法将1
应用于f
。所以我们需要弄清楚表达式trace "f was called" $ (+1)
评估的函数。因此实际调用trace
,其不安全的IO打印和f was called
出现在终端,然后trace
返回其第二个参数:(+1)
。
现在我们知道f
是什么函数:(+1)
。 f
现在将成为该函数的直接引用,如果再次调用trace("f was called")$(+1)
,则无需评估原始代码f
。这就是第二个print
什么都不做的原因。
这种情况完全不同,即使它看起来很相似:
f n=trace("f was called:"++show n)$n+1
这里我们 使用语法糖通过在左侧写入参数来定义函数。让我们看看lambda表示法更清楚地看到与f
绑定的实际值是什么:
f = \n -> trace ("f was called:" ++ show n) $ n + 1
这里我们直接写了一个函数值 ,而不是一个可以计算得到函数的表达式。因此,当f
在1
上调用之前需要进行评估时,f
的值就是整个函数; trace
调用内部函数,而不是函数中调用 result 的东西。因此trace
不会将f
作为评估f 1
的一部分进行调用,而是将其作为评估应用let x = f 1
的一部分进行调用。如果您保存了该结果(例如通过执行f 2
)然后多次打印,则您只能看到一条跟踪。但是,当我们评估trace
时,f
调用仍然存在于f
的函数内部,因此当再次调用trace
时,{ {1}}。