普通函数和 lambda Haskell 函数有什么区别?

What is the difference between normal and lambda Haskell functions?

我是 Haskell 的初学者,我一直在关注电子书 Get Programming with Haskell

我正在学习 Lambda 函数的闭包,但我看不出以下代码的区别:

genIfEven :: Integral p => p -> (p -> p) -> p
genIfEven x = (\f -> isEven f x)

genIfEven2 :: Integral p => (p -> p) -> p -> p
genIfEven2 f x = isEven f x

如果有人能解释一下这里的确切区别是什么就太好了

在基本级别1,“普通”函数和使用 lambda 语法创建的函数之间并没有真正的区别。是什么让您认为问它是什么有什么不同? (在您展示的特定示例中,函数以不同的顺序获取参数,但其他方面相同;它们中的任何一个都可以用 lambda 语法或“正常”语法定义)

函数是 Haskell 中的第一个 class 值。这意味着您可以将它们传递给其他函数,return 它们作为结果,在数据结构中存储和检索它们,等等。就像您可以使用数字、字符串或任何其他值一样。

就像数字、字符串等一样,拥有表示函数值的语法很有帮助,因为您可能想在其他代码中间创建一个简单的函数值。如果你,比方说,需要将 x + 1 传递给某个函数,而你不能只为第一个写文字 1,那将是非常可怕的,你必须转而去文件中的其他地方并且添加一个 one = 1 绑定,以便您可以返回并编写 x + one。以完全相同的方式,您可能需要将加 1 的函数传递给其他函数;在文件的其他地方添加单独的定义 plusOne x = x + 1 会很烦人,因此 lambda 语法为我们提供了一种编写“函数文字”的方法:\x -> x + 1.2

考虑“正常”函数定义语法,如下所示:

incrementAllBy _ [] = []
incrementAllBy n (x:xs) = (x + n) : xs 

这里我们没有任何仅表示 incrementAllBy 是其名称的函数值的源代码。该函数在这种语法中是 implied,分布在(可能)多个“规则”上,这些规则说明我们的函数 returns 给定的值它适用于某种形式的论证。这种语法也从根本上迫使我们将函数绑定到一个名称。所有这些都与直接表示函数本身的 lambda 语法形成对比,没有捆绑的案例分析或名称。

然而,它们只是写函数的不同方式。一旦定义,函数之间就没有区别,无论您使用哪种语法来表达它们。


您提到您正在学习闭包。目前还不清楚这与问题有什么关系,但我猜这有点令人困惑。

我要在这里说一些有点争议的话:你不需要学习闭包。3

闭包是使 incrementAllBy n xs = map (\x -> x + n) xs 之类的东西工作所涉及的东西。这里创建的函数\x -> x + n依赖于n,这是一个参数所以每次incrementAllBy被调用时可以不同,并且可以有多个这样的调用运行同时。所以这个 \x -> x + n 函数不能像顶级函数那样只是程序二进制文件中特定地址的编译代码块。传递给 map 的内存结构必须存储 n 的副本或存储对它的引用。这样的结构称为“闭包”,据说已经“关闭”n,或者“捕获”了它。

在 Haskell 中,您不需要了解这些。我将表达式 \n -> x + n 视为简单地创建一个新的函数值,这取决于值 n(以及值 +,它也是第一个 class 值! ) 恰好在范围内。我不认为你需要考虑这个问题,就像你考虑表达式 x + n 根据本地 n 创建一个新数值一样。 Haskell 中的闭包仅在您尝试了解语言的实现方式时才重要,而不是在 Haskell.

中编程时

闭包确实在命令式语言中很重要。 \x -> x + n(等价于)是否存储对 n 的引用或 n 的副本(以及何时获取副本,以及获取副本的深度)的问题对于理解如何使用此函数的代码有效,因为 n 不仅仅是一种引用值的方式,它是一个随着时间的推移具有(可能)不同值的变量。

但在 Haskell 中,我真的不认为我们应该向初学者传授闭包的术语或概念。它使“您可以从现有值创建新功能,甚至是仅在本地范围内的功能”变得过于复杂。

因此,如果您已将这两个函数作为示例来尝试说明闭包的概念,并且您不明白这个“闭包”有什么不同,您可能可以忽略整个问题并且继续做更重要的事情。


1 有时,您用来编写代码的“等效”语法的选择 确实 会影响操作行为,例如性能。通常(但不总是)这种影响可以忽略不计。作为一个初学者,我强烈建议暂时忽略这些问题,所以我没有提到它们。一旦您对所有语言元素的含义有了透彻的理解,就可以更容易地学习推理代码如何执行所涉及的原则。

它有时也会影响 GHC 推断类型的方式(大多数实际上不是它们是 lambda 的事实,但是如果你像 plusOne = \x -> x + 1 那样绑定没有语法参数的函数名,你可以打破单态限制,但这是许多 Stack Overflow 问题中涉及的另一个主题,因此我不会在这里解决)。

2 在这种情况下,您还可以使用运算符部分编写更简单的函数文字,如 (+1).

3 现在我要教你闭包,这样我就可以解释为什么你不需要了解闭包。 :P

除了一个区别外,没有任何区别:lambda 表达式不需要名称。因此,它们有时在其他语言中被称为“匿名函数”。

如果你打算经常使用一个函数,你会想要给它起一个名字,如果你只需要它一次,lambda 通常就可以了,因为你可以在你使用它的地方定义它。

当然,你可以在之后命名一个匿名函数

genIfEven2 = \f x -> isEven f x

那将完全等同于您的定义。