【问题标题】:What is the difference between normal and lambda Haskell functions?普通和 lambda Haskell 函数有什么区别?
【发布时间】:2021-04-05 03:08:21
【问题描述】:

我是 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

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

【问题讨论】:

  • 我不确定我是否理解您所期待的答案。这是两个不同的功能,需要以不同的方式使用。它们不可互换。
  • 嗯...参数的顺序不同?但这与它是 lambda 还是“正常”的 Haskell 函数无关。是什么促使你试图找出这些之间的差异?除了单态限制之外,这两种语法在含义上并没有真正的区别。
  • 我很好奇isEven 做了什么。
  • @dfeuer 我预测它和genIfEven2, har har 做同样的事情。
  • 您的示例令人困惑,因为除了“正常”与 lambda 之外,还有 更多 差异。

标签: haskell lambda-calculus


【解决方案1】:

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

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

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

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

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

这里我们没有任何源代码只代表incrementAllBy 是一个名称的函数值。该函数在此语法中隐含,分布在(可能)多个“规则”上,这些“规则”说明我们的函数返回的值,因为它应用于某种形式的参数。这种语法也从根本上迫使我们将函数绑定到一个名称。所有这些都与直接表示函数本身的 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(以及值+,这也是一个一流的值!)。我认为你不需要考虑这个问题,而不是考虑表达式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

【讨论】:

    【解决方案2】:

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

    如果您打算经常使用一个函数,您会想要给它一个名称,如果您只需要一次,通常会使用 lambda,因为您可以在使用它的网站上定义它。

    当然,您可以在它诞生之后命名一个匿名函数:

    genIfEven2 = \f x -> isEven f x
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-08
      • 1970-01-01
      • 2018-05-04
      • 1970-01-01
      • 2019-05-24
      • 2020-09-22
      • 1970-01-01
      相关资源
      最近更新 更多