【问题标题】:Does Haskell "understand" curried function definitions?Haskell 是否“理解”柯里化函数定义?
【发布时间】:2018-04-18 22:14:34
【问题描述】:

在 Haskell 中,函数总是有一个参数。多个参数通过Currying 实现。既然如此,我可以看到两个参数的函数如何定义为下面的“func1”。它是一个返回函数(闭包)的函数,它将外部函数的单个参数添加到返回函数的单个参数中。

然而,虽然这是柯里化函数的工作原理,但这并不是定义双参数函数的常规 Haskell 语法。相反,我们被教导定义像“func2”这样的函数。

我想知道 Haskell 如何理解 func2 的行为方式应该与 func1 相同。 func2 的定义并没有告诉我它是一个返回函数的函数。相反,它实际上看起来像一个双参数函数,我们被告知不存在!

这里有什么诀窍? Haskell 是不是刚出生就知道我们可以用这种教科书的方式定义多参数函数,并且它们以我们期望的方式工作?也就是说,这是一个似乎没有明确记录的语法约定(Haskell 知道您的意思,并会为您提供缺少的函数返回),还是有其他一些魔法在起作用或我遗漏了什么?

func1 :: Int -> (Int -> Int)
func1 x = (\y -> x + y)

func2 :: Int -> Int -> Int
func2 x y = x + y

main = do
    print (func1 7 9)
    print (func2 7 9)

【问题讨论】:

  • Haskell 将func2 翻译成func1

标签: haskell


【解决方案1】:

语言本身中,写一个f x y z = _形式的函数定义等价于f = \x y z -> _,也就是等价于f = \x -> \y -> \z -> _。这没有理论上的原因。只是那些嵌套的 lambda 抽象是一个可怕的眼睛/手指疼痛,每个人都认为牺牲一点迂腐来为它制作一些语法糖就可以了。这就是表面上的全部内容,并且可能是您现在需要知道的全部内容。

然而,在语言的实现中,事情变得更加棘手。在最常见的 GHC 实现中,f x y = _f = \x -> \y -> _ 之间实际上是有区别的。当 GHC 编译 Haskell 时,它会将 arity 分配给声明。 f的前一个定义有arity 2,后者有arity 0。从GHC.Base(.)

(.) f g = \x -> f (g x)

(.)2,尽管它的类型 ((b -> c) -> (a -> b) -> a -> c) 表示它最多可以应用三次。这会影响优化:GHC 只会内联 饱和 的函数,或者应用的参数至少与其数量一样多。在调用(maximum .) 中,(.) 不会内联,因为它只有一个参数(它是不饱和)。在调用(maximum . f) 中,它内联到\x -> maximum (f x),而在(maximum . f) 1 中,(.) 将首先内联到一个 lambda 抽象(生成 (\x -> maximum (f x)) 1),它将 beta-减少到maximum (f 1)。如果实现了(.)

(.) f g x = f (g x)

(.) 将有 arity 3,这意味着它会减少内联频率(特别是 f . g 情况,这是高阶函数的一个非常常见的参数),可能会降低性能,这正是评论上面写着:

确保它仅在左侧有两个参数,以便它内联 当应用于两个函数时,即使没有最终参数

最终答案:根据语言的语义,这两种形式应该等价,但是在 GHC 中,两种形式在优化方面具有不同的特征,即使它们总是给出相同的结果。

【讨论】:

  • 谢谢,这很有帮助。 fwiw 我想我明白了为什么这在文献中没有清楚地出现:函数通常在 lambda 表达式之前引入,但是如果不先了解后者,就无法解释这种语法糖。我最终在“Haskell 编程”第 4.5 节“Lambda 表达式”中找到了它的解释,Curried Functions 之后的页面在第 3.6 节中讨论。教程页面haskell.org/tutorial/functions.html 对这个主题保持沉默,尽管它在那里介绍了 lambdas 并讨论了一个有两个参数的函数..
  • Haskell 定义文档擅长回答这类问题。
【解决方案2】:

在谈论类型签名时,没有“多参数函数”之类的东西。所有功能都是单参数,周期。 Haskell 不需要以某种方式将多参数函数“翻译”成单参数函数,因为前者根本不存在。

所有函数类型签名都类似于a -> b,其中a 是参数类型,b 是返回类型。有时b 可能恰好包含更多箭头->,在这种情况下,我们人类(但不是编译器)可能会说函数有多个参数。

当谈到实现的语法时,即f x y = z - 这只是语法糖,在编译期间会被脱糖(即机械转换)为f = \x -> \y -> z

【讨论】:

  • 嗯,多参数函数肯定作为一个概念存在于实现上述脱糖的人的脑海中。您在解释这种脱糖的文档中是否有引文?我在阅读的所有书籍和维基页面中都错过了它,尽管显然必须发生类似的事情(因此我提出了问题)。顺便说一句,当我使用你所说的“翻译”这个词时,我实际上是指你所说的正在发生的事情,你称之为“去糖”。
  • 意识到不是我使用了“翻译”这个词,尽管我很容易做到。脱糖只是句法翻译的一种形式,不是吗?
  • 您在问题中引用了两个不同的类型签名和两个不同的主体定义,并询问 Haskell 如何“知道”它们是等价的。我回答了这两个问题:我的答案的第一部分是关于类型签名,第二部分是关于正文定义。
猜你喜欢
  • 1970-01-01
  • 2011-04-22
  • 2011-05-09
  • 2019-01-28
  • 2013-06-03
  • 2014-04-14
  • 2018-01-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多