两次函数调用但只显示一条轨迹
Two function calls but only one trace displayed
使用 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
我怀疑这些行为与懒惰有关,但我的问题仍然存在:这是预期的行为吗?如果是,为什么?
黑客断言:
The trace function outputs the trace message given as its first
argument, before returning the second argument as its result.
我在第一个示例中没有看到它。
编辑 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
您的跟踪在定义 f
时打印,而不是在调用它时打印。如果您希望跟踪作为调用的一部分发生,您应该确保在收到参数之前不会对其进行评估:
f x = trace "f was called" $ x + 1
此外,当我 运行 你的 TIO 时,我根本看不到痕迹出现。 trace
并不是真正可靠的打印方式,因为它欺骗了构建该语言的 IO 模型。评估顺序中最细微的变化都会扰乱它。当然,对于调试,您可以使用它,但即使是这个简单的示例,也不能保证有多大帮助。
在您的编辑中,您引用了 trace
的文档:
The trace function outputs the trace message given as its first
argument, before returning the second argument as its result.
事实上,这正是您程序中发生的事情!当定义f
,
trace "f was called" $ (+ 1)
需要评估。首先,打印 "f was called" 。然后,trace
的计算结果为 returns、(+ 1)
。这是 trace
表达式的最终值,因此 (+ 1)
就是 f
的定义。 trace
消失了,看到了吗?
确实是懒惰的结果
懒惰意味着仅仅定义一个值并不意味着它会被评估;只有在某些事情需要它时才会发生。如果不需要,则实际生成它的代码不会 "do anything"。如果需要特定值 ,则代码为 运行,但仅在第一次需要;如果有其他引用相同的值并再次使用,这些使用将直接使用第一次产生的值。
您必须记住,函数在任何意义上都是值;适用于普通值的一切也适用于函数。因此,您对 f
的定义只是为一个值编写一个表达式,表达式的求值将被推迟到实际需要 f
的值时,并且由于它需要表达式计算的值(函数)的两倍将被保存并再次使用。
让我们更详细地看一下:
f=trace("f was called")$(+1)
您正在使用一个简单的等式定义一个值 f
(不使用任何语法糖来在等式左侧编写参数,或通过多个等式提供案例)。所以我们可以简单地将右侧作为定义值 f
的单个表达式。只是定义它什么都不做,它会一直坐在那里直到你调用:
print $ f 1
现在 print 需要计算它的参数,所以这会强制表达式 f 1
。但是我们不能在不先强制 f
的情况下将 f
应用于 1
。所以我们需要弄清楚表达式 trace "f was called" $ (+1)
的计算结果是什么函数。所以 trace
实际上被调用,它的不安全 IO 打印和 f was called
出现在终端,然后 trace
returns 它的第二个参数:(+1)
.
所以现在我们知道函数 f
是什么:(+1)
。 f
现在将直接引用该函数,如果再次调用 f
则无需评估原始代码 trace("f was called")$(+1)
。这就是为什么第二个 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
调用是 inside 函数,而不是在函数中调用 result 的东西。所以 trace
不是作为评估 f
的一部分调用的,而是作为评估应用程序 f 1
的一部分调用的。如果您保存了结果(比如 let x = f 1
),然后多次打印,您只会看到一条痕迹。但是当我们计算 f 2
时,trace
调用仍然存在于函数内部,即 f
的值,因此当再次调用 f
时 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
我怀疑这些行为与懒惰有关,但我的问题仍然存在:这是预期的行为吗?如果是,为什么?
黑客断言:
The trace function outputs the trace message given as its first argument, before returning the second argument as its result.
我在第一个示例中没有看到它。
编辑 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
您的跟踪在定义 f
时打印,而不是在调用它时打印。如果您希望跟踪作为调用的一部分发生,您应该确保在收到参数之前不会对其进行评估:
f x = trace "f was called" $ x + 1
此外,当我 运行 你的 TIO 时,我根本看不到痕迹出现。 trace
并不是真正可靠的打印方式,因为它欺骗了构建该语言的 IO 模型。评估顺序中最细微的变化都会扰乱它。当然,对于调试,您可以使用它,但即使是这个简单的示例,也不能保证有多大帮助。
在您的编辑中,您引用了 trace
的文档:
The trace function outputs the trace message given as its first argument, before returning the second argument as its result.
事实上,这正是您程序中发生的事情!当定义f
,
trace "f was called" $ (+ 1)
需要评估。首先,打印 "f was called" 。然后,trace
的计算结果为 returns、(+ 1)
。这是 trace
表达式的最终值,因此 (+ 1)
就是 f
的定义。 trace
消失了,看到了吗?
确实是懒惰的结果
懒惰意味着仅仅定义一个值并不意味着它会被评估;只有在某些事情需要它时才会发生。如果不需要,则实际生成它的代码不会 "do anything"。如果需要特定值 ,则代码为 运行,但仅在第一次需要;如果有其他引用相同的值并再次使用,这些使用将直接使用第一次产生的值。
您必须记住,函数在任何意义上都是值;适用于普通值的一切也适用于函数。因此,您对 f
的定义只是为一个值编写一个表达式,表达式的求值将被推迟到实际需要 f
的值时,并且由于它需要表达式计算的值(函数)的两倍将被保存并再次使用。
让我们更详细地看一下:
f=trace("f was called")$(+1)
您正在使用一个简单的等式定义一个值 f
(不使用任何语法糖来在等式左侧编写参数,或通过多个等式提供案例)。所以我们可以简单地将右侧作为定义值 f
的单个表达式。只是定义它什么都不做,它会一直坐在那里直到你调用:
print $ f 1
现在 print 需要计算它的参数,所以这会强制表达式 f 1
。但是我们不能在不先强制 f
的情况下将 f
应用于 1
。所以我们需要弄清楚表达式 trace "f was called" $ (+1)
的计算结果是什么函数。所以 trace
实际上被调用,它的不安全 IO 打印和 f was called
出现在终端,然后 trace
returns 它的第二个参数:(+1)
.
所以现在我们知道函数 f
是什么:(+1)
。 f
现在将直接引用该函数,如果再次调用 f
则无需评估原始代码 trace("f was called")$(+1)
。这就是为什么第二个 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
调用是 inside 函数,而不是在函数中调用 result 的东西。所以 trace
不是作为评估 f
的一部分调用的,而是作为评估应用程序 f 1
的一部分调用的。如果您保存了结果(比如 let x = f 1
),然后多次打印,您只会看到一条痕迹。但是当我们计算 f 2
时,trace
调用仍然存在于函数内部,即 f
的值,因此当再次调用 f
时 trace
.