它使哪个差,如果我定义与lambda表达式或不与GHC定义函数有/无的lambda
编译模块所以当f :: A -> B
f = \x -> ...
与
f :: A -> B
f x = ...
我的函数认为我看到它帮助编译器内联函数,但除此之外,如果我从第一个版本更改为第二个版本,它会对我的代码产生影响。
我想了解别人的代码,并得到推理为什么这个函数是在第一种方式而不是第二种方式定义。
它使哪个差,如果我定义与lambda表达式或不与GHC定义函数有/无的lambda
编译模块所以当f :: A -> B
f = \x -> ...
与
f :: A -> B
f x = ...
我的函数认为我看到它帮助编译器内联函数,但除此之外,如果我从第一个版本更改为第二个版本,它会对我的代码产生影响。
我想了解别人的代码,并得到推理为什么这个函数是在第一种方式而不是第二种方式定义。
首先,第二种形式更加灵活(它允许您进行模式匹配,其他子句可用于其他情况)。
当只有一个子句时,它实际上等同于一个lambda ...除非您有一个where
范围。也就是说,当你用不同的参数评估f
f = \x -> someCalculation x y
where y = expensiveConstCalculation
比
f x = someCalculation x y
where y = expensiveConstCalculation
更有效,因为在后者,y
总是重新计算。在lambda形式,y
被重新使用:
f
签名是单态,然后f
是constant applicative form,即全局常数。这意味着y
将在整个程序中共享,并且只有someCalculation
需要针对每个f
调用重新完成。这通常是理想的性能明智的,当然这也意味着y
保持占用内存。f
是多态的,那么它实际上隐含了你正在使用的类型的函数。这意味着你不能全球共享,但如果你写了map f longList
,那么在映射到列表之前,仍需要计算一次y
一次。这是性能差异的要点。现在,GHC当然可以重新排列内容,并且由于可以保证结果是相同的,所以如果认为效率更高,它可能总是将一种形式转换为另一种形式。但通常情况并非如此。
为了回答这个问题,我写了一个小程序,有两种方式,看了一眼心中产生:
f1 :: Int -> Int
f1 = \x -> x + 2
{-# NOINLINE f1 #-}
f2 :: Int -> Int
f2 x = x + 2
{-# NOINLINE f2 #-}
我得到运行ghc test.hs -ddump-simpl
核心。相关部分是:
f1_rjG :: Int -> Int
[GblId, Arity=1, Str=DmdType]
f1_rjG =
\ (x_alH :: Int) -> + @ Int GHC.Num.$fNumInt x_alH (GHC.Types.I# 2)
f2_rlx :: Int -> Int
[GblId, Arity=1, Str=DmdType]
f2_rlx =
\ (x_amG :: Int) -> + @ Int GHC.Num.$fNumInt x_amG (GHC.Types.I# 2)
结果是一样的,所以要回答你的问题:从一种形式更改为其他没有影响。
这就是说,我建议看看leftaroundabout的答案,它涉及实际上存在差异的情况。
+1用于演示如何实际检查GHC的产生情况,但两种表单在所有情况下都是相同的,这不是事实。 – leftaroundabout
这个推理的'y'被共享也适用于'let'绑定或者当where子句被使用时是否有区别? – epsilonhalbe
完全的懒惰转型不会照顾效率问题吗? – dfeuer
另外,我非常肯定,GHC如何处理函数内联函数的应用只有在“完全应用”时才会有所不同。内联过早可能会成为问题,我收集,这就是为什么一些列表函数被写入额外的参数。或类似的东西。 – dfeuer