【问题标题】:Scala Stream function evaluationScala Stream 函数评估
【发布时间】:2015-08-16 19:26:16
【问题描述】:

我得到以下代码:

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 的关键区别是什么?

【问题讨论】:

    标签: scala lazy-evaluation lazy-sequences


    【解决方案1】:

    有两点需要考虑:

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

    懒惰

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

    布尔表达式中的短路

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

    两者结合

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

    现在,如果您有 a &amp;&amp; lazyB,它会扩展为 a &amp;&amp; (b0 :: b1 :: lazyB2)。正如您在此处看到的,只要a 或bi 是false,它将返回而不评估语句的正确部分。这就是您的forAll 中发生的情况。

    如何解决

    好消息是修复非常简单:只需交换p(x) 和acc 的顺序:只要p(x) 是true,析取将返回而不评估acc,停止计算。

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

    输出:

    5
    true
    

    【讨论】:

      猜你喜欢
      • 2018-04-04
      • 1970-01-01
      • 1970-01-01
      • 2017-12-28
      • 2017-04-06
      • 1970-01-01
      • 2010-11-14
      • 2012-04-09
      相关资源
      最近更新 更多