2015-08-28 31 views
4

它使哪个差,如果我定义与lambda表达式或不与GHC定义函数有/无的lambda

编译模块所以当
f :: A -> B 
f = \x -> ... 

f :: A -> B 
f x = ... 

我的函数认为我看到它帮助编译器内联函数,但除此之外,如果我从第一个版本更改为第二个版本,它会对我的代码产生影响。

我想了解别人的代码,并得到推理为什么这个函数是在第一种方式而不是第二种方式定义。

回答

5

首先,第二种形式更加灵活(它允许您进行模式匹配,其他子句可用于其他情况)。

当只有一个子句时,它实际上等同于一个lambda ...除非您有一个where范围。也就是说,当你用不同的参数评估f

f = \x -> someCalculation x y 
where y = expensiveConstCalculation 

f x = someCalculation x y 
where y = expensiveConstCalculation 

更有效,因为在后者,y总是重新计算。在lambda形式,y被重新使用:

  • 如果f签名是单态,然后fconstant applicative form,即全局常数。这意味着y将在整个程序中共享,并且只有someCalculation需要针对每个f调用重新完成。这通常是理想的性能明智的,当然这也意味着y保持占用内存。
  • 如果f是多态的,那么它实际上隐含了你正在使用的类型的函数。这意味着你不能全球共享,但如果你写了map f longList,那么在映射到列表之前,仍需要计算一次y一次。

这是性能差异的要点。现在,GHC当然可以重新排列内容,并且由于可以保证结果是相同的,所以如果认为效率更高,它可能总是将一种形式转换为另一种形式。但通常情况并非如此。

+0

这个推理的'y'被共享也适用于'let'绑定或者当where子句被使用时是否有区别? – epsilonhalbe

+2

完全的懒惰转型不会照顾效率问题吗? – dfeuer

+0

另外,我非常肯定,GHC如何处理函数内联函数的应用只有在“完全应用”时才会有所不同。内联过早可能会成为问题,我收集,这就是为什么一些列表函数被写入额外的参数。或类似的东西。 – dfeuer

8

为了回答这个问题,我写了一个小程序,有两种方式,看了一眼心中产生:

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的答案,它涉及实际上存在差异的情况。

+4

+1用于演示如何实际检查GHC的产生情况,但两种表单在所有情况下都是相同的,这不是事实。 – leftaroundabout