两个函数调用但只显示一个跟踪

时间:2017-08-30 18:13:30

标签: haskell ghc trace

使用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

Try it online!

我怀疑这些行为与懒惰有关,但我的问题仍然存在:这是预期的行为,如果是,为什么?

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

2 个答案:

答案 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

这里我们直接写了一个函数值 ,而不是一个可以计算得到函数的表达式。因此,当f1上调用之前需要进行评估时,f的值就是整个函数; trace调用内部函数,而不是函数中调用 result 的东西。因此trace不会将f作为评估f 1的一部分进行调用,而是将其作为评估应用let x = f 1的一部分进行调用。如果您保存了该结果(例如通过执行f 2)然后多次打印,则您只能看到一条跟踪。但是,当我们评估trace时,f调用仍然存在于f的函数内部,因此当再次调用trace时,{ {1}}。