Haskell:带函数参数的箭头优先级

时间:2015-08-21 20:33:48

标签: function haskell recursion arguments operator-precedence

我是相对经验丰富的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 来自哪里?返回的函数在哪里?

3 个答案:

答案 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)

您对 Function Currying 的工作原理感到困惑。

考虑以下(++)的函数定义。

采用两个参数,生成一个列表:

(++) :: [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

来自第二个等式。