如何 "read" Elm 的 -> 运算符

How to "read" Elm's -> operator

我真的很喜欢 Elm,直到遇到一个我以前从未见过的函数,想了解它的输入和输出。

foldl的声明为例:

foldl : (a -> b -> b) -> b -> List a -> b

我看着这个,不禁觉得好像缺少一组括号,或者关于此运算符的关联性的其他一些微妙之处(我找不到任何明确的文档) .也许这只是更多地使用该语言直到我得到一个 "feel" 的问题,但我想有一种方法可以用英语 "read" 这个定义。

查看 docs 中的示例...

foldl (::) [] [1,2,3] == [3,2,1]

我希望函数签名是这样的:

Given a function that takes an a and a b and returns a b, an additional b, and a List, foldl returns a b.

对吗?

对于像我这样迫切希望输入用逗号分隔并且 inputs/outputs 更清楚地分隔的人,您有什么建议?

简答

您要找的缺少括号是因为 -> 是右结合的:类型 (a -> b -> b) -> b -> List a -> b 等同于 (a -> b -> b) -> (b -> (List a -> b))。非正式地,在 -> 的链中,读取最后一个 -> 之前的所有内容作为参数,并且只读取最右边的内容作为结果。

长答案

您可能缺少的关键见解是 currying —— 如果您有一个带有两个参数的函数,您可以用一个带有第一个参数和 [=77= 的函数来表示它] 一个接受第二个参数然后 returns 结果的函数。

例如,假设您有一个函数 add,它接受两个整数并将它们相加。在 Elm 中,您可以编写一个函数,将两个元素作为一个元组并将它们相加:

add : (Int, Int) -> Int
add (x, y) = x+y

你可以称它为

add (1, 2)  -- evaluates to 3

但是假设您没有元组。你可能认为没有办法写这个函数,但实际上使用柯里化你可以把它写成:

add : Int -> (Int -> Int)
add x =
  let addx : Int -> Int
      addx y = x+y
  in
    addx

也就是说,您编写一个接受 x 的函数和 returns 另一个接受 y 并将其添加到原始 x 的函数。你可以用

来调用它
((add 1) 2)  -- evaluates to 3

您现在可以用两种方式来考虑 add:将 xy 相加的函数, 作为一个 "factory" 函数,它采用 x 值并生成新的、专门的 addx 函数,这些函数仅采用一个参数并将其添加到 x.

"factory"思考问题的方式偶尔会派上用场。例如,如果你有一个名为 numbers 的数字列表,你想给每个数字加 3,你可以调用 List.map (add 3) numbers;如果您编写的是元组版本,则必须编写类似 List.map (\y -> add (3,y)) numbers 的内容,这有点尴尬。

Elm 源自一种编程语言的传统,这种编程语言非常喜欢这种思考函数的方式并在可能的情况下鼓励这种方式,因此 Elm 的函数语法旨在使其变得简单。为此,-> 是右结合的:a -> b -> c 等价于 a -> (b -> c)。这意味着如果你不加括号,你定义的是一个接受 a 和 returns 和 b -> c 的函数,我们可以再次将其视为接受的函数一个 a 和一个 b 和 returns 一个 c 或等效的 一个接受 a 和 returns一个b -> c.

还有另一个有助于调用这些函数的语法细节:函数应用程序是 left-associative。这样,上面那个丑陋的 ((add 1) 2) 就可以写成 add 1 2 了。通过这种语法调整,除非您想部分应用一个函数,否则您根本不必考虑柯里化——只需使用所有参数调用它,语法就会生效。