关于scala:懒惰的评估步骤:过滤列表

Lazy evaluation steps : filtering a list

我对scala中的懒惰评估有一些问题。这是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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"返回)?为什么不继续评估,直到清单结束?


这似乎与viewlast的实施有关。如果这样做,它将不会重新启动:

1
2
3
4
people.view
      .filter(isOlderThan17(_))
      .filter(nameStartsWithJ(_))
      .fold(None)((x, y) => Some(y))

看起来last试图访问head两次,在这种情况下,您需要找到people.view.filter(isOlderThan17(_))的第一个元素两次,因此view必须重新计算两次。

更新

这是TraversableLikelast的定义:

1
2
3
4
5
6
def last: A = {
    var lst = head
    for (x <- this)
      lst = x
    lst
  }

第一个元素确实被访问了两次。


Why did the evaluation have to be restarted (back again from"Mark")
after"Jill" had been found?

因为每个filter都会产生一个只包含筛选项的新集合。使用流可避免此问题:

1
2
3
4
println(people.toStream
              .filter(p => isOlderThan17(p))
              .filter(p => nameStartsWithJ(p))
              .last)

从流vs视图vs迭代器

Stream is a lazy list indeed. In fact, in Scala, a Stream is a List
whose tail is a lazy val. Once computed, a value stays computed and is
reused. Or, as you say, the values are cached.

在这种情况下,您只需要组成过滤器,所以您也可以将两个谓词组合起来,只进行一次过滤:

1
2
println(people.filter(p => isOlderThan17(p) && nameStartsWithJ(p))
              .last)