我是Haskell和函数式编程的新手。我正在阅读真实世界的Haskell,我意识到我对一些例子感到困惑。
具体来说,这可以在第9章的“谓词的域特定语言”一节中,这些示例包含w x y z参数。
我把它归结为:
为什么这段代码会编译?
f :: Int -> (Int -> Int)
f x y = x+y
main = do
let q = f 4 5
putStr (show (q))
根据类型签名,f
显然接受1个参数并返回一个函数。
但是,似乎我可以编写函数方程,因此它将接受两个参数并返回一个int。
为什么这可能?这是否意味着忽略了类型签名?
f
函数正在采用多个参数而不是一个参数!
此外,任何回答的人都可以提供指向此能力的某种Haskell文档的链接(如果可能的话)。
编辑:
在考虑了一段时间之后,你们两个似乎暗示的是:
1)这个语法是语法糖,f总是只有一个参数,无论在等式中写入多少参数
2)在应用f时,函数体将(总是?)转换为stub(实际上是返回的函数),其中x固定为给定的参数(4),y是参数。
3)然后将这个新函数应用于5,取代y,然后评估+函数。
我真正感兴趣的是,它究竟是什么意思“在函数方程中,如果你写了多个参数,它真的是语法糖,以下实际发生......”正如我上面写的那样。 或者除了我以外,每个人都这么明显吗?
编辑II:
真正令人大开眼界的回答是在下面的@luqui评论中,不幸的是我认为我不能将评论标记为答案。
这是事实 f x y = ... 实际上是语法糖: f = \ x - > \ y - > ...
对我而言,下面所有其他人都说过这一点。
我在Haskell的Gentle Introduction中找到了一个这样的源代码,这里:3.1节中的http://haskell.cs.yale.edu/tutorial/functions.html,称为Lambda Abstractions。
事实上,方程式:
inc x = x + 1 添加x y = x + y
真的是简写:
inc = \ x - > X + 1 add = \ x y - > X + Y
虽然它不使用短语“syntactic sugar”,但它使用了更多,呃,数学导向的单词“shorthand”,但作为一名程序员,我将其视为“糖”: - )
答案 0 :(得分:10)
正在讨好。正如类型签名所示,f
只接受一个参数。那将是4
。然后它返回一个函数,该函数立即应用于5
。实际上,这两种类型的签名:
Int -> Int -> Int
和
Int -> (Int -> Int)
在Haskell中是等效的。
编辑:我在您提供的页面中找到的Partial Application链接解释了这一点。
编辑2:您询问了Haskell的currying行为的定义。我不知道这是否是你要找的:Haskell 98 Revised Report部分3.3 Curried Applications and Lambda Abstractions中说:
函数应用程序编写为
e1 e2
。应用程序关联到左侧,因此(f x) y
中可省略括号。
答案 1 :(得分:4)
->
运算符是右关联运算符,即t -> t -> t
与t -> (t -> t)
相同。
如果你重写
f x y = x+y
以等效形式
f x = \y -> x + y
这种意见应该变得明显。
答案 2 :(得分:2)
这绝对有点令人讨厌。虽然我无法立即在文档中找到它明确说明这种行为的位置,但我们所要做的就是查看我们关于currying的数学知识。
我们知道,带有签名Int ->(Int -> Int)
的函数需要Int
,并返回一个函数,它接受Int
并返回一个Int。我们可以提供获得最终int所需的Int
两个,就像你的例子一样:
f :: Int -> (Int -> Int)
f x y = x+y
如果我们只提供第一个参数,我们会得到一个需要另一个参数的函数。咖喱的面包和黄油。
简单地说,curry是right-associative。换句话说,Int -> (Int -> Int)
与Int->Int->Int
相同,只是我们添加了括号以使其更明显:
f 3
不缺少参数,但实际上返回类型为Int->Int
的函数。
当你学习associative property的补充时,有点像回到数学中。
3 + 2 + 1 = (3 + 2) + 1 = 3 + (2 + 1)
无论我们如何设置括号,结果都是一样的。
答案 3 :(得分:2)
这不是真正的语法糖,它只是函数应用程序在Haskell中的工作原理。
考虑:
f :: Int -> Int -> Int -> Int
f x y z = x + y + z
g = f 4
h = g 4 5
f 4 4 5 -- 13
g 4 5 -- 13
g 6 -- 13
您可以在ghci中使用此功能进行确认。 g
是函数f
的部分应用 - 其类型为g :: Int -> Int -> Int
。
你也可以写:
((f 4) 4) 5 -- 13
在这种情况下,(f 4)
返回一个部分应用的函数,它接受另外两个参数,((f 4) 4)
返回一个部分应用的函数,该函数接受一个参数,整个表达式减少到13。
答案 4 :(得分:2)
在考虑了更多这方面后,我认为完整的解释应该是:
Haskell函数只能接受单个参数并返回一个参数。 Haskell允许我们假装传递了几个参数,但是这个表单被视为一系列嵌套的lambda函数。
f x y = x + y
被视为
(1) f = \x -> \y -> x + y
这种处理对于lambda函数也是如此 \ x y - > x + y 被视为 \ x - > \ y - > x + y
这允许我们将类型声明视为左关联,即: f :: Int - > Int - >诠释 实际上是 f ::(Int - >(Int - > Int)) 它完全适合上面的(1):f没有参数,但返回一个接受Int的函数。该函数依次返回一个接受另一个Int的函数,并返回一个Int。
这意味着如果我们想从函数返回一个函数,我们就不必做任何特殊的事情了,因为那是Haskell的“默认”模式。
这也意味着给定了类型声明 f :: Int - > Int - >诠释 我们可以用0,1或2个参数写出f的贯环(“方程式”)。如果指定了一个或两个参数,编译器将生成必要的lambdas以符合表单 f ::(Int - >(Int - > Int))
f = \x -> \y -> x + y
f x = \y -> x + y -- \x -> is generated
f x y = x + y -- \x -> \y is generated
但在每种情况下,似乎接受两个参数的函数应用程序将成功编译,因为它将始终转换为第一种形式,例如
f 4 5 --> (\x -> (\y -> x + y) 5 ) 4
内部函数应用程序将返回curried形式(x + 5)
这可以启用部分功能应用程序,我们可以只提供一个参数并返回部分功能。
另外,更改函数类型的优先级:
f'' :: (Int -> Int) -> Int
更改含义 - 我们接受一个函数获取一个Int并返回一个作为单个参数,并返回一个Int 假设我们已经在某处定义了这个函数,那么用Integer参数调用这个函数,例如
f'' 4 5
不会编译。
编辑:
此外,即使最后一个参数在括号中,或者是一个类型声明,它仍然存在。
,例如,在下面,最后一对括号是可选的,因为如果它们不在那里,编译器就会把它们放到“lambda'd”形式。
t4 :: (Int -> Int) -> (Int -> Int)
t4 f i = f i + i
可以这样应用:
t4 (\x -> x*10) 5
另外,鉴于:
type MyIntInt = Int -> Int
然后:
t5 :: MyIntInt -> MyIntInt
相当于t4,但令人困惑,因为MyIntInt的含义在两个地方都不同。 第一个是第一个参数的类型 第二个被“扩展”为Int - > Int(可能是因为操作符的右关联性),这意味着t5在函数方程和函数应用程序中似乎可以接受0到2个参数(实际上总是接受0并返回嵌入的lambdas,如上所述) )。
E.g。我们可以像t4一样编写t5:
t5 f i = f i + i