我对Scala中的延迟评估有一些疑问。 这是示例代码:
val people=List(("Mark", 32), ("Bob", 22), ("Jane", 8),
("Jill", 21), ("nick", 50), ("Nancy", 42),
("Mike", 19), ("Sara", 12), ("Paula", 42),
("John", 21))
def isOlderThan17(person: (String,Int)) = {
println(s"isOlderThan 17 called for $person")
val(_,age) = person
age > 17
}
def nameStartsWithJ(person: (String, Int)) = {
println(s"isNameStartsWithJ called for $person")
val (name,_) = person
name.startsWith("J")
}
println(people.view.filter(p => isOlderThan17(p))
.filter(p => nameStartsWithJ(p))
.last)
输出:
isOlderThan 17 called for (Mark,32)
isNameStartsWithJ called for (Mark,32)
isOlderThan 17 called for (Bob,22)
isNameStartsWithJ called for (Bob,22)
isOlderThan 17 called for (Jane,8)
isOlderThan 17 called for (Jill,21)
isNameStartsWithJ called for (Jill,21)
isOlderThan 17 called for (Mark,32) //Here is the problem.
isNameStartsWithJ called for (Mark,32)
isOlderThan 17 called for (Bob,22)
isNameStartsWithJ called for (Bob,22)
isOlderThan 17 called for (Jane,8)
isOlderThan 17 called for (Jill,21)
isNameStartsWithJ called for (Jill,21)
isOlderThan 17 called for (nick,50)
isNameStartsWithJ called for (nick,50)
isOlderThan 17 called for (Nancy,42)
isNameStartsWithJ called for (Nancy,42)
isOlderThan 17 called for (Mike,19)
isNameStartsWithJ called for (Mike,19)
isOlderThan 17 called for (Sara,12)
isOlderThan 17 called for (Paula,42)
isNameStartsWithJ called for (Paula,42)
isOlderThan 17 called for (John,21)
isNameStartsWithJ called for (John,21)
(John,21)
为什么在“Jill”被发现后必须重新开始评估(从“Mark”开始)?为什么不在评估结束前继续评估?
答案 0 :(得分:3)
这似乎与last
中view
的实施有关。如果你这样做,它就不会重新开始:
people.view
.filter(isOlderThan17(_))
.filter(nameStartsWithJ(_))
.fold(None)((x, y) => Some(y))
last
似乎试图访问head
两次,在您的情况下,您需要找到people.view.filter(isOlderThan17(_))
的第一个元素两次,因此view
必须重新计算它两次。
<强>更新强>
以下是last
中TraversableLike
的定义:
def last: A = {
var lst = head
for (x <- this)
lst = x
lst
}
第一个元素确实被访问了两次。
答案 1 :(得分:1)
为什么必须重新开始评估(再次来自&#34; Mark&#34;) 之后&#34;吉尔&#34;已被发现?
因为每个filter
都会生成一个仅包含过滤项的新集合。使用流来避免这种情况:
println(people.toStream
.filter(p => isOlderThan17(p))
.filter(p => nameStartsWithJ(p))
.last)
来自Stream vs Views vs Iterators
Stream确实是一个懒惰的列表。实际上,在Scala中,Stream是一个List 谁的尾巴是懒惰的。一旦计算出来,值就会保持计算并且是 重复使用。或者,如您所说,值被缓存。
在这种情况下,你只是编写过滤器,所以你也可以只组合两个谓词并只进行一次过滤:
println(people.filter(p => isOlderThan17(p) && nameStartsWithJ(p))
.last)