我想知道为什么左侧折叠所需的函数具有类型签名a -> b -> a
而不是b -> a -> a
。这背后有设计决定吗?
例如,在Haskell中,我必须编写foldl (\xs x -> x:xs) [] xs
来反转列表而不是更短的foldl (:) [] xs
(这可以通过b -> a -> a
)。另一方面,有些用例需要标准a -> b -> a
。在Scala中,可以添加:xs.foldLeft(List.empty[Int]) ((xs, x) => xs:+x)
,可以写为xs.foldLeft(List.empty[Int]) (_:+_)
。
按比例增加需要给定类型签名而不是替代签名的用例,还是有其他决策导致左侧折叠设计在Haskell和Scala(可能还有很多其他语言)中?
答案 0 :(得分:28)
从概念上讲,右侧折叠,例如foldr f z [1..4]
替换以下形式的列表
:
/ \
1 :
/ \
2 :
/ \
3 :
/ \
4 []
使用以下形式的表达式的值
f
/ \
1 f
/ \
2 f
/ \
3 f
/ \
4 z
如果我们要在一行上表示此表达式,则所有括号都将与右侧相关联,因此名称右侧折叠:(1 `f` (2 `f` (3 `f` (4 `f` z))))
。左折叠在某种意义上是右折叠。特别是,我们希望左侧折叠的相应图形的形状是左侧折叠的镜像,如下所示:
f
/ \
f 4
/ \
f 3
/ \
f 2
/ \
z 1
如果我们要在一行上写出这个图表,我们会得到一个表达式,其中所有括号都与左边相关联,这与左折叠的名称相吻合:
((((z `f` 1) `f` 2) `f` 3) `f` 4)
但请注意,在此镜像图中,折叠的递归结果作为第一个参数提供给f
,而列表的每个元素作为第二个参数提供,即参数被提供给{ {1}}与右侧折叠相反。
答案 1 :(得分:7)
类型签名是foldl :: (a -> b -> a) -> a -> [b] -> a
;组合函数在左侧具有初始值是很自然的,因为这是它与列表元素结合的方式。同样地,你会注意到foldr
反之亦然。你反向定义的复杂性是因为你正在使用一个lambda表达式,其中flip
会更好:foldl (flip (:)) [] xs
,它在翻转和反向概念之间也具有令人愉快的相似性。
答案 2 :(得分:4)
因为您以简短形式为(a /: bs)
撰写foldLeft
;这是一个将a
推送到所有bs
的运算符,因此以相同的方式编写函数(即(A,B) => A
)是很自然的。请注意,foldRight
按其他顺序执行此操作。
答案 3 :(得分:2)
说你有这个:
List(4, 2, 1).foldLeft(8)(_ / _)
这与:
相同((8 / 4) / 2) / 1
看第一个参数总是如何累加?按此顺序使用参数会使占位符语法(下划线)直接转换为扩展表达式。