我是相对经验丰富的Haskell程序员,只有几个小时的经验,所以答案可能很明显。
看完Haskell的味道之后,当Simon解释追加(++)
函数真正如何与其参数一起工作时,我迷路了。
所以,here's the part where he talks about this。
首先,他说(++) :: [a] -> [a] -> [a]
可以理解为一个函数,它将两个列表作为参数,并在最后一个箭头后返回一个列表。但是,他补充说实际上是,这样的事情发生了:(++) :: [a] -> ([a] -> [a])
,该函数只接受一个参数并返回一个函数。
我不确定返回的函数闭包如何获得第一个列表,因为它也需要一个参数。
在演示文稿的下一张幻灯片中,我们有以下实现:
(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
如果我认为(++)收到两个参数并返回一个列表,那么这段代码和递归就足够清楚了。
如果我们认为(++)
只收到一个参数并返回一个列表, ys
来自哪里?返回的函数在哪里?
答案 0 :(得分:4)
理解这一点的技巧是所有haskell函数最多只占用1个参数,只是类型签名和语法糖中的隐含括号使它看起来好像有更多的参数。以++
为例,以下转换都是等效的
xs ++ ys = ...
(++) xs ys = ...
(++) xs = \ys -> ...
(++) = \xs -> (\ys -> ...)
(++) = \xs ys -> ...
另一个简单的例子:
doubleList :: [Int] -> [Int]
doubleList = map (*2)
这里我们有一个参数doubleList
的函数,没有任何显式参数。它本来相当于写
doubleList x = map (*2) x
或以下任何
doubleList = \x -> map (*2) x
doubleList = \x -> map (\y -> y * 2) x
doubleList x = map (\y -> y * 2) x
doubleList = map (\y -> y * 2)
doubleList
的第一个定义是用通常称为无点符号的方式编写的,因为在支持它的数学理论中,参数被称为“点”,所以无点是“没有”参数”。
一个更复杂的例子:
func = \x y z -> x * y + z
func = \x -> \y z -> x * y + z
func x = \y z -> x * y + z
func x = \y -> \z -> x * y + z
func x y = \z -> x * y + z
func x y z = x * y + z
现在,如果我们想要完全删除对参数的所有引用,我们可以使用执行函数组合的.
运算符:
func x y z = (+) (x * y) z -- Make the + prefix
func x y = (+) (x * y) -- Now z becomes implicit
func x y = (+) ((*) x y) -- Make the * prefix
func x y = ((+) . ((*) x)) y -- Rewrite using composition
func x = (+) . ((*) x) -- Now y becomes implicit
func x = (.) (+) ((*) x) -- Make the . prefix
func x = ((.) (+)) ((*) x) -- Make implicit parens explicit
func x = (((.) (+)) . (*)) x -- Rewrite using composition
func = ((.) (+)) . (*) -- Now x becomes implicit
func = (.) ((.) (+)) (*) -- Make the . prefix
因为你可以看到有很多不同的方法来编写一个具有不同数量的显式“参数”的特定函数,其中一些是非常可读的(即func x y z = x * y + z
)而另一些只是一个混乱意义不大的符号(即func = (.) ((.) (+)) (*)
)
答案 1 :(得分:2)
考虑以下(++)
的函数定义。
采用两个参数,生成一个列表:
(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
采用一个参数,生成一个函数,取一个列表并生成一个列表:
(++) :: [a] -> ([a] -> [a])
(++) [] = id
(++) (x:xs) = (x :) . (xs ++)
如果仔细观察,这些函数将始终产生相同的输出。通过删除第二个参数,我们已将返回类型从[a]
更改为[a] -> [a]
。
(++)
提供两个参数,我们会得到[a]
[a] -> [a]
这称为函数currying。我们不需要为具有多个参数的函数提供所有参数。如果我们提供的参数总数少于参数总数,而不是得到“具体”结果([a]
),我们得到一个函数作为结果,可以获取剩余的参数([a] -> [a]
)。
答案 2 :(得分:2)
也许这会有所帮助。首先让我们在没有操作符号的情况下编写它,这可能会令人困惑。
append :: [a] -> [a] -> [a]
append [] ys = ys
append (x:xs) ys = x : append xs ys
我们可以一次应用一个参数:
appendEmpty :: [a] -> [a]
appendEmpty = append []
我们可以等同地写出
appendEmpty ys = ys
来自第一个等式。
如果我们应用非空的第一个参数:
-- Since 1 is an Int, the type gets specialized.
appendOne :: [Int] -> [Int]
appendOne = append (1:[])
我们可以等同地写下
appendOne ys = 1 : append [] ys
来自第二个等式。