好吧,我想,迟到总比没有好。
comp1
并且comp2
不仅在语义上是等价的,甚至在句法上也是等价的。
在定义的等号的 LHS 上写参数只是语法糖,所以这两个函数是等价的:
id1 x = x
id2 = \x -> x
编辑:我意识到我并没有真正回答你的问题,所以你在这里:
当这些用INLINE
编译指示注释时,GHC 会有所不同,因为 GHC 在其核心表示中存储函数的展开以及它可以展开的数量(这就是Guidance=ALWAYS_IF(arity=1,...)
部分),因此它在实践中实际上很重要。
我不认为它确实如此,因为在脱糖到核心后无法区分 和 ,所有优化都在核心上运行comp1
。comp2
因此,当 GHC 想要创建一个新的展开时,它可能会为明显的 arity(例如前导 lambda 的数量)这样做。
内联对不饱和绑定大多没有好处,见下文。示例实际上也是如此comp1
:我们希望发生这种情况的原因并不是我们关心消除函数调用。相反,我们希望comp1
专门化和f
参数g
,而不管x
我们将专门化应用于什么具体。实际上有一个优化过程应该做这种工作,称为构造函数专门化(更多内容见下文)。在这里使用甚至完全不合适:这INLINE
仍然不会专门化像.comp1 (const 5)
const 5
因此,这不会有太大的改变,只要你不给每个 let-bound 的东西都撒上INLINE
pragma。即使那样,这是否带来任何好处也是值得怀疑的:关键是,在没有任何进一步动机(例如,将函数专门化为具体参数)的情况下,内联不饱和调用是没有意义的,而且它只会使代码大小膨胀某个点,所以它甚至可能会使事情变慢。
结束编辑
我认为为什么不内联对绑定的不饱和调用的一个原因是它们大多不会带来任何新的优化机会。
f = \x y -> 1 + (x * y)
g = \x y -> (1 + x) * y
内联f 16
yield \y -> 1 + (16*y)
,这并不比f 16
. 相反,代码大小显着增加(这是内联的最大缺点)。
现在,如果有这样的调用g 16
会产生\y -> (1 + 16) * y
优化到\y -> 17 * y
. 但是这些机会会被另一个优化过程、构造函数或调用模式专业化检测到。这里的见解是,1 + x
如果我们知道 的值,则可以简化x
。由于我们g
使用文字(例如值)进行调用,因此专门g
针对该特定调用站点是有益的,例如g16 = \y -> 17 *y
. 无需内联g
,其他呼叫站点也可能共享为g16
.
这只是在仍然拥有高效代码的同时不需要进行内联的一个示例。还有许多其他优化可以与内联实现你想要的。例如,Eta-expansion 将确保调用尽可能饱和:
main = print (f 2)
f = g 1
g x y = x + y
由于f
总是用 1 个参数调用,我们可以对其进行 eta 扩展:
f eta = g 1 eta
现在调用g
已饱和,可以内联。Dito for f
,所以最终这减少到
main = print 3
f eta = 1 + eta
g x y = x + y
模死代码消除。