在进行共享实验时,我发现预定义的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的求值,而对x2x3进行两次求值。由于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生成的代码将以完全无关紧要的方式运行,基于您可能不会想到的编译的次要方面。

09-06 01:35