使用/不使用lambdas定义函数

eps*_*lbe 4 lambda haskell ghc

如果我使用lambda表达式定义函数或者在使用GHC编译模块时没有这样做会有什么不同

f :: A -> B
f = \x -> ...
Run Code Online (Sandbox Code Playgroud)

f :: A -> B
f x = ...
Run Code Online (Sandbox Code Playgroud)

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

我试图了解其他人的代码,并在第一种方式而非第二种方式中推断出为什么要定义此函数的原因.

mad*_*jar 8

为了回答这个问题,我用两种方式编写了一个小程序,并查看了生成的Core:

f1 :: Int -> Int
f1 = \x -> x + 2
{-# NOINLINE f1 #-}

f2 :: Int -> Int
f2 x = x + 2
{-# NOINLINE f2 #-}
Run Code Online (Sandbox Code Playgroud)

我通过运行获得核心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)
Run Code Online (Sandbox Code Playgroud)

结果是相同的,所以回答你的问题:从一种形式转换到另一种形式没有影响.


话虽如此,我建议查看leftaroundabout的答案,该答案涉及实际存在差异的情况.

  • +1用于演示如何实际检查GHC产生的内容,但两种形式在所有情况下都是相同的并不正确. (4认同)

lef*_*out 5

首先,第二种形式更灵活(它允许您进行模式匹配,下面的其他条款用于替代案例).

当只有一个子句时,它实际上相当于一个lambda ......除非你有一个where范围.也就是说,

f = \x -> someCalculation x y
 where y = expensiveConstCalculation
Run Code Online (Sandbox Code Playgroud)

效率比

f x = someCalculation x y
 where y = expensiveConstCalculation
Run Code Online (Sandbox Code Playgroud)

因为在后者中,y当您f使用不同的参数进行评估时,总是会重新计算.在lambda形式中,y重新使用:

  • 如果签名f是单态的,则f是一个不变的应用形式,即全局常数.这意味着y在整个程序中共享,只someCalculation需要为每次调用重新完成f.这通常是理想的性能,当然它也意味着y不断占用内存.
  • 如果fs是多态的,那么它实际上是隐含的是你正在使用它的类型的函数.这意味着你没有获得全局共享,但是如果你编写例如map f longList,那么y在映射到列表之前仍需要计算一次.

这是性能差异的要点.现在,当然GHC可以重新排列内容,因为它保证结果是相同的,如果认为效率更高,它可能总是将一种形式转换为另一种形式.但通常它没有.

  • 完全懒惰转型难道不会解决这种效率问题吗? (2认同)