Reputation: 103
I have some questions regarding lazy evaluation in Scala. This is the sample code:
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)
Output :
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)
Why did the evaluation have to be restarted (back again from "Mark") after "Jill" had been found? Why didn't it continue the evaluation until the end of the list?
Upvotes: 2
Views: 1448
Reputation: 7076
Why did the evaluation have to be restarted (back again from "Mark") after "Jill" had been found?
Because each filter
results in a new collection that contains only the filtered items. Use a stream to avoid this:
println(people.toStream
.filter(p => isOlderThan17(p))
.filter(p => nameStartsWithJ(p))
.last)
From Stream vs Views vs Iterators
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.
In this case you're only composing filters so you might as well just combine the two predicates and do the filtering only once:
println(people.filter(p => isOlderThan17(p) && nameStartsWithJ(p))
.last)
Upvotes: 1
Reputation: 9225
It seems to be related to the implementation of last
in view
. If you do it this way it won't restart:
people.view
.filter(isOlderThan17(_))
.filter(nameStartsWithJ(_))
.fold(None)((x, y) => Some(y))
Looks like last
is trying to access head
twice, which in your case means you need to find the first element of people.view.filter(isOlderThan17(_))
twice and so view
has to recompute it two times.
UPDATE
Here's the definition of last
in TraversableLike
:
def last: A = {
var lst = head
for (x <- this)
lst = x
lst
}
The first element is indeed accessed twice.
Upvotes: 3