Scala:写foldLeft的常用做法

时间:2017-02-24 09:55:09

标签: scala functional-programming

在功能编程中,有两个重要的方法foldLeftfoldRight。以下是foldRight

的实现
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tails: List[A]) extends List[A]

 def foldRight[A, B](ls: List[A], z: B)(f: (A, B) => B): B = ls match {
    case Nil => z
    case Cons(x, xs) => f(x, foldRight(xs, z)(f))
  }

以下是foldLeft的实施:

  @annotation.tailrec
  def foldLeft[A, B](ls: List[A], z: B)(f: (B, A) => B): B = ls match {
    case Nil => z
    case Cons(x, xs) => foldLeft(xs, f(z, x))(f)
  }
}

我的问题是:我从很多文档中读到,他们经常把f函数的顺序放在:f: (B, A) => B而不是f: (A, B) => B。为什么这个定义更好?因为如果我们使用其他方式,它将与foldLeft具有相同的签名,并且会更好。

2 个答案:

答案 0 :(得分:3)

因为foldLeft“绕过”遍历中的cons结构:

foldRight(Cons(1, Cons(2, Nil)), z)(f) ~> f(1, f(2, z))
                                            ^  ^
                                            A  B
foldLeft(Cons(1, Cons(2, Nil)), z)(f) ~> f(f(z, 2), 1)
                                           ^        ^
                                           B        A

由于它以相反的顺序访问了这些类型,所以这些类型也传统上被翻转。当然可以交换参数,但如果你有一个非交换操作,并期望“传统”的行为,你会感到惊讶。

答案 1 :(得分:1)

  

...如果我们以其他方式使用,它将与foldLeft具有相同的签名,并且会更好。

不,我认为这不会更好。如果它们具有相同的签名,那么如果您打算使用一个签名但不小心输入另一个签名,则编译器将无法捕获它。

A / B顺序也方便地提醒我B(初始或"零")值与A元素集合的关系。

foldLeft: B-->>A, A, A, ...   // (f: (B, A) => B)
foldRight: ... A, A, A<<--B   // (f: (A, B) => B)