8

我是 Haskell 的新手,也是函数式编程的新手。在其他(除了 Haskell)语言中,lambda 形式通常非常有用。

例如,在方案中:

(define (deriv-approx f)
  (lambda (h x)
    (/ (- (f (+ x h)
          (f x)
       h)))

将创建一个闭包(在函数 f 上)以逼近一个导数(在值 x 处,间隔为 h)。然而,由于它的部分应用,在 Haskell 中似乎不需要这种 lambda 形式的使用:

deriv-approx f h x = ( (f (x + h)) - (f x) ) / h

在 Haskell 中有哪些需要 lambda 形式的例子?

编辑:用“lambda 形式”替换“闭包”

4

4 回答 4

12

我将给出两个稍微间接的答案。

首先,考虑以下代码:

module Lambda where

derivApprox f h x = ( (f (x + h)) - (f x) ) / h

我已经编译了这个,同时告诉 GHC 转储一个中间表示,它大致是作为编译过程的一部分使用的 Haskell 的简化版本,以获得这个:

Lambda.derivApprox
  :: forall a. GHC.Real.Fractional a => (a -> a) -> a -> a -> a
[LclIdX]
Lambda.derivApprox =
  \ (@ a) ($dFractional :: GHC.Real.Fractional a) ->
    let {
      $dNum :: GHC.Num.Num a
      [LclId]
      $dNum = GHC.Real.$p1Fractional @ a $dFractional } in
    \ (f :: a -> a) (h :: a) (x :: a) ->
      GHC.Real./
        @ a
        $dFractional
        (GHC.Num.- @ a $dNum (f (GHC.Num.+ @ a $dNum x h)) (f x))
        h

如果您看过去杂乱无章的注释和冗长,您应该能够看到编译器已将所有内容都转换为 lambda 表达式。我们可以认为这表明您可能不需要手动执行此操作。

相反,让我们考虑一下您可能需要 lambda 的情况。这是一个使用折叠来组成函数列表的函数:

composeAll :: [a -> a] -> a -> a
composeAll = foldr (.) id

那是什么?看不到 lambda!事实上,我们也可以走另一条路:

composeAll' :: [a -> a] -> a -> a
composeAll' xs x = foldr (\f g x -> f (g x)) id xs x

这不仅充满了 lambda,它还为 main 函数提供了两个参数,更重要的是,它适用foldr于所有参数。foldr将, ,的类型(a -> b -> b) -> b -> [a] -> b与上述比较;显然它需要三个参数,但上面我们已经将它应用于四个!更不用说累加器函数有两个参数,但我们这里有一个三参数 lambda。当然,诀窍在于两者都返回一个带有单个参数的函数。我们只是在现场应用这个论点,而不是在周围玩弄 lambdas。

希望所有这些都让您相信这两种形式是等价的。Lambda 形式从来都不是必需的,或者可能总是必需的,因为谁能分辨?

于 2011-08-18T22:35:20.760 回答
6

之间没有语义上的区别

f x y z w = ...

f x y = \z w -> ...

表达式风格(显式 lambda)和声明风格之间的主要区别在于语法。重要的一种情况是当您想使用where子句时:

f x y = \z w -> ...
   where ... -- x and y are in scope, z and w are not

确实可以编写任何 Haskell 程序,而无需在任何地方使用显式 lambda,只需将它们替换为命名的本地函数或部分应用程序。

另请参阅:声明与表达风格

于 2011-08-18T22:31:38.500 回答
3

当您可以声明命名的柯里化函数(例如您的 Haskell deriv-approx)时,永远不需要使用显式的 lambda 表达式。每个显式 lambda 表达式都可以替换为命名函数的部分应用程序,该函数将 lambda 表达式的自由变量作为其第一个参数。

为什么要在源代码中执行此操作并不容易看出,但某些实现本质上就是以这种方式工作的。

此外,有点题外话,以下重写(与我刚才描述的不同)是否算作避免 lambdas?

deriv-approx f = let myfunc h x = (f(x+h)-(f x))/h in myfunc
于 2011-08-18T22:27:09.370 回答
0

如果您只使用一次函数,例如作为 map 或 foldr 或其他一些高阶函数的参数,那么使用 lambda 通常比使用命名函数更好,因为很明显没有使用该函数其他任何地方——不可能,因为它没有名字。当您引入一个新的命名函数时,您可以让阅读您的代码的人在作用域期间记住另一件事。所以严格来说 lambdas 从来都不是必需的,但它们通常比替代方案更可取。

于 2011-08-21T13:19:37.843 回答