我真的很喜欢榆树,直到我遇到一个我以前从未见过的功能并想要了解它的输入和输出。
以foldl
的声明为例:
foldl : (a -> b -> b) -> b -> List a -> b
我看着这个,不禁感觉好像有一组我遗漏的括号,或者其他一些关于这个操作符的关联性的细微之处(我找不到任何明确的文档)。也许这只是一个使用语言的问题,直到我得到它的“感觉”,但我想有一种方法用英语“读”这个定义。
查看docs ...
中的示例foldl (::) [] [1,2,3] == [3,2,1]
我希望函数签名读取如下内容:
如果函数需要
a
和b
并返回b
,则会额外b
和List
,{{1} }返回foldl
。
这是对的吗?
对于像我这样迫切希望输入被逗号划分并且输入/输出更清楚地分开的人,你能给出什么建议?
答案 0 :(得分:10)
您正在寻找的缺失括号是由于->
是右关联的:(a -> b -> b) -> b -> List a -> b
类型等同于(a -> b -> b) -> (b -> (List a -> b))
。非正式地,在->
s的链中,在最后->
之前读取所有内容作为参数,并且只读取最右边的内容。
你可能缺少的关键见解是currying - 如果你有一个带有两个参数的函数,你可以用一个带有第一个参数的函数来表示它并返回一个带有第二个参数然后返回结果。
例如,假设您有一个函数add
,它接受两个整数并将它们加在一起。在Elm中,您可以编写一个函数,将两个元素作为元组并添加它们:
add : (Int, Int) -> Int
add (x, y) = x+y
你可以把它称为
add (1, 2) -- evaluates to 3
但是假设你没有元组。您可能认为没有办法编写此函数,但实际上使用currying可以将其写为:
add : Int -> (Int -> Int)
add x =
let addx : Int -> Int
addx y = x+y
in
addx
也就是说,编写一个带x
的函数并返回另一个带y
的函数并将其添加到原始x
。你可以用
((add 1) 2) -- evaluates to 3
现在,您可以通过两种方式考虑add
:作为一个功能,可以使用x
和y
并添加它们,或作为“factory”函数,它接受x
个值并生成新的,专用的addx
函数,这些函数只接受一个参数并将其添加到x
。
思考事物的“工厂”方式每隔一段时间就派上用场。例如,如果您有一个名为numbers
的号码列表,并且您想为每个号码添加3,那么您只需拨打List.map (add 3) numbers
;如果您编写了元组版本,则必须编写类似List.map (\y -> add (3,y)) numbers
的内容,这有点尴尬。
Elm来自编程语言的传统,它非常喜欢这种思考函数的方式,并尽可能地鼓励它,因此Elm的函数语法旨在使其变得简单。为此,->
是右关联的:a -> b -> c
等同于a -> (b -> c)
。这意味着如果你没有括号,你所定义的是一个函数,它接受一个a
并返回一个b -> c
,我们再次认为这个函数需要{{1} }和a
并返回b
,或等效一个带c
并返回a
的函数。
还有另一种语法准确可以帮助调用这些函数:函数应用程序 left -associative。这样,上面的丑陋b -> c
可以写成((add 1) 2)
。通过这种语法调整,除非你想部分应用一个函数,否则你根本不需要考虑currying - 只需用所有参数调用它,语法就可以解决了。