Haskell中的类型签名与函数方程

时间:2011-01-22 14:45:56

标签: types signature haskell

我是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。 为什么这可能?这是否意味着忽略了类型签名?

这是在哭吗?这是某种封闭吗? 如果我正确地理解了这个http://www.haskell.org/haskellwiki/Currying,那么它似乎与那里定义的currying有些相反 - 我的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”,但作为一名程序员,我将其视为“糖”: - )

5 个答案:

答案 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 -> tt -> (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