使用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上的结果相同:在线试用!
编辑但是这个程序:
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
我怀疑这些行为与懒惰有关,但我的问题仍然存在:这是预期的行为吗?如果是,为什么?
黑客断言:
跟踪函数将输出作为其第一个参数给出的跟踪消息,然后返回第二个参数作为结果。
我在第一个示例中没有看到它。
编辑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
实际需要该值时为止,并且因为它需要两倍于值(函数)的值,该表达式计算的值将被保存并第二次重用。
让我们更详细地看一下:
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
呼叫是内部的功能是被称为到的东西,而不是产生一个功能。因此trace
,不被称为评估的一部分f
,而是被称为评估应用程序的一部分f 1
。如果您保存了该结果(例如执行let x = f 1
),然后多次打印,则只会看到一条迹线。但是当我们进行求值时f 2
,trace
调用仍然存在于函数内部,即f
因此,当f
再次被调用时也是如此trace
。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句