So I\'ve spent hours trying to work out exactly how this code produces prime numbers.
lazy val ps: Stream[Int] = 2 #:: Stream.from(3).filter(i =>
ps.ta
Your explanations are mostly correct, you made only two mistakes:
takeWhile
doesn't include the last checked element:
scala> List(1,2,3).takeWhile(_<2)
res1: List[Int] = List(1)
You assume that ps
always contains only a two and a three but because Stream
is lazy it is possible to add new elements to it. In fact each time a new prime is found it is added to ps
and in the next step takeWhile
will consider this new added element. Here, it is important to remember that the tail of a Stream
is computed only when it is needed, thus takeWhile
can't see it before forall
is evaluated to true.
Keep these two things in mind and you should came up with this:
ps = [2]
i = 3
takeWhile
2*2 <= 3 -> false
forall on []
-> true
ps = [2,3]
i = 4
takeWhile
2*2 <= 4 -> true
3*3 <= 4 -> false
forall on [2]
4%2 > 0 -> false
ps = [2,3]
i = 5
takeWhile
2*2 <= 5 -> true
3*3 <= 5 -> false
forall on [2]
5%2 > 0 -> true
ps = [2,3,5]
i = 6
...
While these steps describe the behavior of the code, it is not fully correct because not only adding elements to the Stream
is lazy but every operation on it. This means that when you call xs.takeWhile(f)
not all values until the point when f
is false are computed at once - they are computed when forall
wants to see them (because it is the only function here that needs to look at all elements before it definitely can result to true, for false it can abort earlier). Here the computation order when laziness is considered everywhere (example only looking at 9):
ps = [2,3,5,7]
i = 9
takeWhile on 2
2*2 <= 9 -> true
forall on 2
9%2 > 0 -> true
takeWhile on 3
3*3 <= 9 -> true
forall on 3
9%3 > 0 -> false
ps = [2,3,5,7]
i = 10
...
Because forall
is aborted when it evaluates to false, takeWhile
doesn't calculate the remaining possible elements.