首页 > 解决方案 > 为什么预定义的 `const` 函数在某些上下文中的行为与等效的 lambda 函数不同?

问题描述

在进行一些共享实验时,我发现预定义const函数在某些情况下的行为不同。

f :: (() -> Int) -> Int
f g = g () + g ()

x1 = f (const   (trace "x1" 42))
x2 = f (\_ ->   (trace "x2" 42))
x3 = f (myconst (trace "x3" 42))

myconst :: a -> b -> a
myconst x _ =  x

此示例在没有优化的情况下编译时,评估仅x1触发一次评估,而对andtrace进行两次评估。由于 lambda 函数,这是合理的。x2x3x2

x1
x2
x2
x3
x3
252

然而,的定义表明const它是一个普通的函数定义,没有任何编译器注释可以解释差异。因此,该函数myconst的行为应该相同,但事实并非如此。如何解释这种行为,有没有办法在这方面影响编译器?

标签: haskelllambdasharing

解决方案


如果myconst编译成单独的模块(即使单独的模块是用 编译的-O0),那么输出是:

x1
x2
x2
x3
252

不同之处在于——在代码中——在单独的模块中-O0调用会生成代码:myconst

let x' = myconst (trace "x3" 42) in x' + x'

myconst但是在同一个模块中调用它就像这样:

trace "x4" 42 + trace "x4" 42

编译-O2完全改变了代码——所有内容都是内联的,并且跟踪被提升到表达式的顶部,所以它们每个只执行一次。

在这方面,您可以清楚地影响编译器,例如通过是否放入myconst单独的模块,或者 - 正如@leftroundabout 所指出的 - 通过添加各种内联编译指示。

我不认为您可以在这方面可靠地影响编译器,而且我不确定您可以通过研究未优化的编译输出来了解多少真实世界的 GHC 代码。我认为上面的示例清楚地表明,-O0基于编译的次要方面,生成的代码将以完全任意和矛盾的方式运行,而这些次要方面您并不认为重要。


推荐阅读