Scala Stream功能评估

时间:2015-08-16 19:26:16

标签: scala lazy-evaluation lazy-sequences

我收到了以下代码:

trait Stream[+A] {
  def uncons: Option[(A, Stream[A])]
  def foldRight[B](z: => B)(f: (A, => B) => B): B = {
    uncons.map(t => {
      f(t._1, t._2.foldRight(z)(f))
    }).getOrElse(z)
  }
  def exists(p: A => Boolean) =
    foldRight(false)((x, acc) => acc || p(x))
  def forAll(p: A => Boolean) =
    foldRight(true)((x, acc) => p(x) && acc)
}

object Stream {
  def cons[A](h: => A, t: => Stream[A]): Stream[A] =
    new Stream[A] {
      lazy val uncons = Some((h, t))
    }
}

然后我以惰性方式创建一个Stream并调用exists方法来检查评估了哪些流元素:

  println(Stream.cons({println("5"); 1}, Stream.cons({println("6"); 2}, Stream.cons({println("7"); 3}, Stream.cons({println("8"); 4}, Stream.empty)))).exists(_ == 1))

我看到的是:

5
6
7
8
true

因此,尽管只有第一个元素足够,但所有元素都得到了评估。我似乎理解为什么exists按照它的方式行事。

然后我运行以下代码:

println(Stream.cons({println("13"); 1}, Stream.cons({println("14"); 2}, Stream.cons({println("15"); 3}, Stream.cons({println("16"); 4}, Stream.empty)))).forAll(_ < 2))

并查看以下内容:

13
14
false

因此,只要forAll遇到不满意的值,它就会终止遍历。

但为什么forAll会这样做?它与exists之间的关键区别是什么?

1 个答案:

答案 0 :(得分:1)

有两件事需要考虑:

  • acc
  • 的类型
  • 布尔表达式中p(x)的顺序。

懒惰

如果您将acc的类型更改为B,则无法在任何一种方法中快速(或短路)失败。您必须知道它,因为您的代码广泛使用惰性,但=> B类型的变量只有在需要其值时才会被评估,即在某个表达式中使用。在这种情况下,acc是通过流计算的结果的 future 。只有你试着看它,这个未来才会发生。因此,为了防止评估整个流,您必须防止这个未来被看到。

布尔表达式中的短路

这是p(x)的顺序很重要的地方。在表达式a && b中,如果afalse,那么我们知道整个连词也是false,因此Scala不会尝试评估b,因为它毫无意义。

合并两个

现在如果你的一个操作数是一个懒惰的表达式会发生什么?好吧,如果您有lazyA || b,Scala会从左到右阅读表达式并评估lazyA。在您的情况下,lazyA表示下一个元素和流的其余部分的累积。因此,lazyA扩展为a0 :: lazyA1,扩展为a0 :: a1 :: lazyA2。因此,您将最终计算整个流,仅用于计算布尔值binop的左侧部分。

现在,如果您有a && lazyB,则展开为a && (b0 :: b1 :: lazyB2)。正如您在此处看到的那样,只要abifalse,就会在不评估语句的正确部分的情况下返回。这就是forAll

中发生的情况

如何解决

好消息是修复非常简单:只需交换p(x)acc的订单:只要p(x)true,拆分就会返回没有评估acc,停止计算。

def exists(p: A => Boolean) = foldRight(false)((x, acc) => p(x) || acc)

输出:

5
true