简体   繁体   English

Scala Stream功能评估

[英]Scala Stream function evaluation

I got a following code: 我得到以下代码:

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))
    }
}

Then I create a Stream in a lazy manner and invoke exists method to check what stream elements were evaluated: 然后,我以一种懒惰的方式创建一个Stream并调用exists方法来检查评估了哪些stream元素:

  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))

And what I see is: 我看到的是:

5
6
7
8
true

So all the elements were evaluated in spite of only first one would be enough. 因此,尽管仅第一个就足够了,但所有元素都进行了评估。 I seem to understand why exists acts the way it does. 我似乎理解为什么exists行为会如此。

Then I run the following code: 然后我运行以下代码:

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))

and see the following: 并查看以下内容:

13
14
false

So as far as forAll comes across a non-satisfying value it terminates the traversal. 因此,只要forAll遇到一个不令人满意的值,它将终止遍历。

But why forAll acts that way? 但是,为什么forAll行为都是这样? What's the crucial difference between it and exists ? 它与exists之间的关键区别是什么?

There are two things to consider : 有两件事要考虑:

  • the type of acc acc的类型
  • the order of p(x) in the boolean expression. 布尔表达式中p(x)的顺序。

Laziness 怠惰

If you change the type of acc to B , you won't be able to fail-fast (or short-circuit) in either of your methods. 如果将acc的类型更改为B ,则两种方法都将无法快速失败(或短路)。 You must know it since your code extensively uses laziness, but a variable of type => B will get evaluated only when its value is required ie used in some expression. 您必须知道这一点,因为您的代码广泛使用了惰性,但是=> B类型的变量仅在需要其值(即在某些表达式中使用)时才会得到评估。 In this case, acc is the future of the result computed over the stream. 在这种情况下, acc是在流上计算的结果的未来 This future will happen only if you try looking at it. 仅当您尝试着看时,才会发生这种未来。 Thus, to prevent the whole stream to be evaluated, you must prevent this future to be looked at. 因此,为了防止对整个流进行评估,您必须避免关注这个未来。

Short-circuiting in boolean expressions 布尔表达式中的短路

This is where the order of p(x) matters. 这就是p(x)的顺序重要的地方。 In the expression a && b , if a is false then we know the whole conjunction is also false , thus Scala won't try evaluating b because it's pointless. 在表达式a && b ,如果afalse那么我们知道整个连接也是false ,因此Scala不会尝试评估b因为它毫无意义。

Combining the two 两者结合

Now what happens if one of your operands is a lazy expression ? 现在,如果您的一个操作数是一个惰性表达式,会发生什么? Well, if you have lazyA || b 好吧,如果您有lazyA || b lazyA || b , Scala will read the expression from left to right and evaluate lazyA . lazyA || b ,Scala从左到右读取表达式并评估lazyA In your case, lazyA represents the accumulation of the next element and the rest of the stream. 在您的情况下, lazyA表示下一个元素和流的其余部分的累积。 Thus, lazyA expands to a0 :: lazyA1 , which expands to a0 :: a1 :: lazyA2 . 因此, lazyA扩展为a0 :: lazyA1 ,后者扩展为a0 :: a1 :: lazyA2 You will therefore end up computing the whole stream just for computing the left part of your boolean binop. 因此,您最终将只计算布尔binop的左侧部分就计算出整个流。

Now, if you have a && lazyB , this expands to a && (b0 :: b1 :: lazyB2) . 现在,如果您有a && lazyBa && lazyB扩展为a && (b0 :: b1 :: lazyB2) As you see here, as soon as a or bi is false , this will return without evaluating the right part of the statement. 如您在此处看到的,一旦abifalse ,它将返回而不评估语句的正确部分。 This is what happens in your forAll . 这就是您的forAll发生的情况。

How to fix it 如何修复

The good news is that the fix is very easy : just swap the order of p(x) and acc : as soon as p(x) is true , the disjunction will return without evaluating acc , stopping the computation. 好消息是修复非常容易:只需交换p(x)acc的顺序: p(x)true ,析取将返回而不评估acc ,从而停止计算。

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

Output : 输出:

5
true

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM