为什么预定义的“const”函数在某些上下文中的行为与等效的 lambda 函数不同?

Why does the predefined `const` function behave differently than the equivalent lambda function in certain contexts?

在进行一些共享实验时,我发现预定义的 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

this example 在没有优化的情况下编译时,计算 x1 只会触发一次 trace 的计算,而 x2 和 [=16= 会计算两次].由于 lambda 函数,这对于 x2 是合理的。

x1
x2
x2
x3
x3
252

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

如果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 生成的代码将根据编译的次要方面以完全任意和矛盾的方式运行,而您不希望这些方面很重要。