定义函数 with/without lambda

defining functions with/without lambdas

在使用 GHC 编译模块时,使用或不使用 lambda 表达式定义函数会有什么不同

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

对比

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

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

我试图理解别人的代码,并试图理解为什么这个函数是以第一种方式而不是第二种方式定义的原因。

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

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 的回答,其中涉及实际存在差异的情况。

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

当只有一个子句时,它实际上等同于一个 lambda...除非你有一个 where 作用域。即,

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

更有效率
f x = someCalculation x y
 where y = expensiveConstCalculation

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

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

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