在Haskell函数中,总是需要一个参数.通过Currying实现多个参数.既然如此,我可以看到两个参数的函数如何定义为下面的"func1".它是一个函数,它返回一个函数(闭包),它将外部函数的单个参数添加到返回函数的单个参数中.
但是,虽然这是curry函数的工作方式,但这不是定义双参数函数的常规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)
Run Code Online (Sandbox Code Playgroud)
在语言本身中,编写表单的函数定义f x y z = _等同于f = \x y z -> _,相当于f = \x -> \y -> \z -> _.这没有理论上的理由; 只是那些嵌套的lambda抽象是一个可怕的眼睛/手指疼痛,每个人都认为牺牲一些迂腐为它制作一些语法糖就好了.这就是表面上的所有内容,现在可能只需要知道.
然而,在语言的实现中,事情变得棘手.在GHC中,这是最常见的实现,实际上f x y = _和之间存在差异f = \x -> \y -> _.当GHC编译Haskell时,它会为声明分配arity.前者的定义是farity 2,后者是arity 0.就拿(.)从GHC.Base
(.) f g = \x -> f (g x)
Run Code Online (Sandbox Code Playgroud)
(.)有arity 2,即使它的type((b -> c) -> (a -> b) -> a -> c)表示它可以应用三次.这会影响优化:GHC只会内联一个饱和的函数,或者至少应该为其arity应用尽可能多的参数.在调用中(maximum .),(.)不会内联,因为它只有一个参数(它是不饱和的).在呼叫(maximum . f)中,将内联到\x -> maximum (f x),并且在(maximum . f) 1中,(.)将第一内联到一个lambda抽象(产生(\x -> maximum (f x)) 1),这将β-内减少到maximum (f 1).如果(.)实施了
(.) f g x = f (g x)
Run Code Online (Sandbox Code Playgroud)
(.)会有arity 3,这意味着它不会经常内联(特别是这种f . g情况,这是高阶函数的一个非常常见的参数),可能会降低性能,这正是它的评论所说的:
确保它只在左侧有两个args,以便在应用于两个函数时进行内联,即使没有最终参数
最终答案:根据语言的语义,这两种形式应该是等价的,但在GHC中,两种形式在优化方面具有不同的特征,即使它们总是给出相同的结果.