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
来自第二个等式。
我是一个相对有经验的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
来自第二个等式。