[英]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 : 有两件事要考虑:
acc
acc
的类型 p(x)
in the boolean expression. p(x)
的顺序。 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.
因此,为了防止对整个流进行评估,您必须避免关注这个未来。
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
,如果a
为false
那么我们知道整个连接也是false
,因此Scala不会尝试评估b
因为它毫无意义。
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 && lazyB
, a && 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. 如您在此处看到的,一旦
a
或bi
为false
,它将返回而不评估语句的正确部分。 This is what happens in your forAll
. 这就是您的
forAll
发生的情况。
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.