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

Haskell: arrow precedence with function arguments

我是一个相对有经验的Haskell程序员,有几个小时的经验,所以答案可能很明显。

看完 Haskell 的味道后,当 Simon 解释 append (++) 函数 really 如何处理它的参数时,我迷路了。

所以,here's the part where he talks about this

首先,他说 (++) :: [a] -> [a] -> [a] 可以理解为一个函数,它获取两个列表作为参数,return 是最后一个箭头后面的列表)。但是,他补充说 实际上 ,会发生这样的事情:(++) :: [a] -> ([a] -> [a]),该函数只需要 一个 参数和 return这是一个函数。

我不确定 returned 函数闭包如何获得第一个列表,因为它也需要一个参数。

在演示文稿的下一张幻灯片中,我们有以下实现:

(++) :: [a] -> [a] -> [a]

[]     ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)

如果我认为 (++) 接收两个参数和 return 一个列表,这段代码连同递归就足够清楚了。

如果我们考虑 (++) 只接收一个参数并且 return 是一个列表, ys 从? returned 函数在哪里?

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

考虑以下 (++).

的函数定义

接受两个参数,生成一个列表:

(++) :: [a] -> [a] -> [a]
[]     ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)

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

(++) :: [a] -> ([a] -> [a])
(++) []     = id
(++) (x:xs) = (x :) . (xs ++)

如果仔细观察,这些函数将始终产生相同的输出。通过删除第二个参数,我们将 return 类型从 [a] 更改为 [a] -> [a]

  • 如果我们向 (++) 提供两个参数,我们将得到 [a]
  • 类型的结果
  • 如果我们只提供一个参数,我们会得到 [a] -> [a]
  • 类型的结果

这称为函数柯里化。我们不需要为具有多个参数的函数提供所有参数。如果我们提供的参数少于参数总数,我们将得到一个函数作为结果,而不是得到一个 "concrete" 结果([a]),它可以接受剩余的参数([a] -> [a])。

理解这一点的诀窍是所有 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 的第一个定义是用通常所说的无点表示法编写的,之所以这样称呼是因为在支持它的数学理论中,参数被称为 "points",所以点-免费是 "without arguments".

一个更复杂的例子:

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

因此,正如您所看到的,有许多不同的方法可以使用不同数量的显式 "arguments" 来编写特定函数,其中一些非常可读(即 func x y z = x * y + z),而另一些则只是一堆没有意义的符号(即 func = (.) ((.) (+)) (*)

也许这会有所帮助。首先让我们在没有运算符符号的情况下编写它,这可能会造成混淆。

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

来自第二个等式。