Haskell“理解”咖喱函数定义吗?

时间:2017-11-06 17:08:31

标签: haskell

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

然而,虽然这是curry函数工作的方式,但这不是定义双参数函数的常规Haskell语法。相反,我们被教导定义像“func2”这样的函数。

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

这里的诀窍是什么? Haskell刚出生,知道我们可以用这种教科书的方式定义多参数函数,并且它们按照我们预期的方式工作吗?也就是说,这是一个似乎没有明确记录的语法约定(Haskell知道你的意思并将为你提供缺失的函数返回),还是有一些其他魔法在工作或我缺少什么?< / p>

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)

2 个答案:

答案 0 :(得分:6)

语言中,编写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只会内联饱和的函数,或者至少应该为其arity应用尽可能多的参数。在调用(maximum .)中,(.)不会内联,因为它只有一个参数(它是不饱和)。在调用(maximum . f)中,内联到\x -> maximum (f x),在(maximum . f) 1中,(.)将首先内联到lambda抽象(生成{ {1}}),将beta减少到(\x -> maximum (f x)) 1。如果maximum (f 1)已实施

(.)

(.) f g x = f (g x) 将具有arity (.),这意味着它会更少地内联(特别是3情况,这是高阶函数的一个非常常见的参数),可能会降低性能,这正是对它的评论所说的:

  

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

最终答案:根据语言的语义,两个表单应该等效,但在GHC中,两种表单在优化方面具有不同的特性,即使它们总是给出同样的结果。

答案 1 :(得分:4)

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

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

在谈论实现的语法时,即f x y = z - 这只是语法糖,在编译过程中被糖化(即机械转换)到f = \x -> \y -> z