折叠为什么期望(a - > b - > a)而不是(b - > a - > a)?

时间:2012-09-05 19:56:03

标签: scala haskell functional-programming

我想知道为什么左侧折叠所需的函数具有类型签名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(可能还有很多其他语言)中?

4 个答案:

答案 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

看第一个参数总是如何累加?按此顺序使用参数会使占位符语法(下划线)直接转换为扩展表达式。