Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Streams not acting lazy?

I know streams are supposed to be lazily evaluated sequences in Scala, but I think I am suffering from some sort of fundamental misunderstanding because they seem to be more eager than I would have expected.

In this example:

 val initial = Stream(1)
 lazy val bad = Stream(1/0)
 println((initial ++ bad) take 1)

I get a java.lang.ArithmeticException, which seems to be cause by zero division. I would expect that bad would never get evaluated since I only asked for one element from the stream. What's wrong?

like image 461
recursive Avatar asked Nov 16 '12 04:11

recursive


3 Answers

OK, so after commenting other answers, I figured I could as well turn my comments into a proper answer.

Streams are indeed lazy, and will only compute their elements on demand (and you can use #:: to construct a stream element by element, much like :: for List). By example, the following will not throw any exception:

(1/2) #:: (1/0) #:: Stream.empty

This is because when applying #::, the tail is passed by name so as to not evaluate it eagerly, but only when needed (see ConsWrapper.# ::, const.apply and class Cons in Stream.scala for more details). On the other hand, the head is passed by value, which means that it will always be eagerly evaluated, no matter what (as mentioned by Senthil). This means that doing the following will actually throw a ArithmeticException:

(1/0) #:: Stream.empty

It is a gotcha worth knowing about streams. However, this is not the issue you are facing.

In your case, the arithmetic exception happens before even instantiating a single Stream. When calling Stream.apply in lazy val bad = Stream(1/0), the argument is eagerly executed because it is not declared as a by name parameter. Stream.apply actually takes a vararg parameter, and those are necessarily passed by value. And even if it was passed by name, the ArithmeticException would be triggered shortly after, because as said earlier the head of a Stream is always early evaluated.

like image 142
Régis Jean-Gilles Avatar answered Nov 16 '22 10:11

Régis Jean-Gilles


The fact that Streams are lazy doesn't change the fact that method arguments are evaluated eagerly.

Stream(1/0) expands to Stream.apply(1/0). The semantics of the language require that the arguments are evaluated before the method is called (since the Stream.apply method doesn't use call-by-name arguments), so it attempts to evaluate 1/0 to pass as the argument to the Stream.apply method, which causes your ArithmeticException.

There are a few ways you can get this working though. Since you've already declared bad as a lazy val, the easiest is probably to use the also-lazy #::: stream concatenation operator to avoid forcing evaluation:

val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial #::: bad) take 1)
// => Stream(1, ?)
like image 35
DaoWen Avatar answered Nov 16 '22 12:11

DaoWen


The Stream will evaluate the head & remaining tail is evaluated lazily. In your example, both the streams are having only the head & hence giving an error.

like image 4
Senthil Kumar Vaithiyanathan Avatar answered Nov 16 '22 12:11

Senthil Kumar Vaithiyanathan