经过多年的OOP,我正在努力学习haskell。我正在读Happy Haskell。它提供了以下代码:
plus :: Int -> Int -> Int
plus x y = x + y
plus' :: Int -> Int -> Int
plus' = \x -> \y -> x + y
increment :: Int -> Int
increment = plus 1
increment' :: Int -> Int
increment' = (\x -> \y -> x + y) 1
我理解加号和加号是如何工作的(它们是相同的,不同的语法)。但增量,我不明白。
increment :: Int -> Int
意味着它需要一个int,并返回一个int,对吗?但就在那之后,实际的功能是:
increment = plus 1
题:
整数值增量在哪里?不应该在x
标志的右边有=
或其他东西,表示该函数作为输入的整数值?就像是:
increment _ = plus 1 x
编辑:此外,不应该增量的定义是Int -> (Int -> Int)
,因为它需要一个int
并将其传递给一个接受int
并返回和int
的函数?
在Haskell中,您可以使用currying和部分应用函数。看看Haskell Wiki: Partial Application
特别是,如果你看一下任何函数的类型签名,它的输入(参数)和它的输出之间没有真正的区别,这是因为你的函数plus :: Int -> Int -> Int
实际上是一个函数,当给定一个Int时,它将返回另一个函数它本身获取剩余的参数并返回int:Int -> Int
。这称为部分应用
这意味着当你调用increment = plus 1
时,你说增量等于 - 记住部分应用程序 - 一个函数(由plus 1
返回),它本身取一个整数并返回一个整数。
由于Haskell是一种函数式编程语言,所有具有相同的东西不是赋值,而更像是一个定义,因此理解部分应用程序的简单方法实际上是遵循等号:
increment = plus 1 =
plus 1 y = 1 + y
如您所见,部分应用程序可用于定义更具体的功能,例如将1添加到一个更具体的数字,而不仅仅是添加两个数字。它还允许更多地使用无点样式,在这种情况下,您可以连接多个函数。
另请注意,使用中缀函数lke (+)
,您可以部分应用于左侧或右侧,这对非交换函数非常有用,例如
divBy2 :: Float -> Float
divBy2 = (/2)
div2by :: Float -> Float
div2by = (2/)
Prelude> divBy2 3
1.5
Prelude> div2by 2
1.0
这将是increment x = plus 1 x
,但一般foo x = bar x
与foo = bar
是相同的,因为如果f
是一个函数,只要用任何参数g(x)
调用返回x
,那么f
与g
功能相同。所以increment = plus 1
的工作原理也一样。
这是因为Haskell中的所有函数都是隐含的curried。因此,返回带参数的函数的函数和带有两个返回值的参数的函数之间没有区别(两者都具有类型a -> a -> a
†)。因此,使用太少的参数调用plus
(或任何其他函数)只会返回一个新函数,其中应用了已经给定的参数。在大多数语言中,这将是一个参数错误。另见point-free style。
†Haskell类型签名是右关联的,因此a -> a -> a -> a
等同于a -> (a -> (a -> a))
。
plus
和plus'
的例子很有启发性。你看到后者似乎没有参数,至少在等号的左边:
plus' :: Int -> Int -> Int
plus' = \x -> \y -> x + y
让我们制作另一对增量版本(我将它们命名为“碰撞”一个数字后面的数字),这些版本在你给出的最终版本的一半之后:
bump :: Int -> Int
bump y = 1 + y
bump' :: Int -> Int
bump' = \y -> 1 + y
这两个定义之间的类比就像plus
和plus'
之间的类比,所以这些应该是有意义的,包括后者,即使它在等号的左侧没有正式的论据。
现在,您对bump'
的理解与您在问题中理解时需要了解increment'
完全相同!实际上,我们将bump'
定义为等于increment'
等于的东西。
那就是(我们很快就会看到),bump'
定义的右侧,
\y -> 1 + y
是等于的东西
plus 1
这两个符号或表达式是两种语法方式,用于定义“获取数字并返回一个以上的函数”。
是什么让他们平等?!好吧,(正如其他答复者所解释的那样)表达式plus 1
被部分应用。从某种程度上说,编译器知道plus
需要两个参数(它毕竟是以这种方式声明的),因此当它出现在这里只应用于一个参数时,编译器知道它还在等待一个。它表示通过给你一个函数“等待”,说,如果再给出一个参数,无论是现在还是以后,这样做会使这个东西完全应用,程序实际上将跳转到plus
的函数体(因此计算x + y
对于给出的两个论点,来自1
表达式的文字plus 1
和后面给出的“一个更多”论点
Haskell的快乐和价值的一个关键部分是将功能本身视为事物,这些事物可以传递并且非常灵活地从一个变为另一个。部分应用程序就是这样一种转换一件事的方式(一个带有“太多参数”的函数,当你想要修复额外的值时)到“恰好多少”的函数中。您可以将部分应用的函数传递给需要特定数量参数的接口。或者你可能只想根据一个通用定义定义多个专门的函数(因为我们可以定义一般的plus
和更具体的函数,如plus 1
和plus 7
)。