为什么预定义的“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
然而,const
的 definition 表明它是一个普通的函数定义,没有任何可以解释差异的编译器注释。因此,函数 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
生成的代码将根据编译的次要方面以完全任意和矛盾的方式运行,而您不希望这些方面很重要。
在进行一些共享实验时,我发现预定义的 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
然而,const
的 definition 表明它是一个普通的函数定义,没有任何可以解释差异的编译器注释。因此,函数 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
生成的代码将根据编译的次要方面以完全任意和矛盾的方式运行,而您不希望这些方面很重要。