14

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?

3 Answers 3

21

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.

Sign up to request clarification or add additional context in comments.

Comments

21

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, ?) 

6 Comments

@Senthil makes a good point in his answer that the head of a Stream is always eagerly evaluated, so if you moved the division-by-zero code into the tail it would work: 1 #:: (1/0) #:: Stream.empty. However, if you're calling the Stream factory method then it will still break for the same reason I explained above.
Saying that the language requires that arguments are evaluated before calling the method is very misleading. It is true for by value parameters, but scala has by name parameters. The very feature that allows to implement the #:: method as some kind of lazy operator. It just happens that Stream.apply has varargs arguments, which are necessarily passed by value (and thus evaluated before the call as you explained)
@RégisJean-Gilles - The language still evaluates all parameters before calling the function because it's a requirement of the underlying JVM—even by-name arguments. The difference is that by-name arguments are wrapped in a Lambda, and the body of the Lambda isn't evaluated until you call it.
Not true. What happens under the hood in the JVM is not the same as the semantics of the language. A by name parameter is under the hood implemented as a Function0, but => T is still not the same as () => T at the language level.
In fact => T is not even a first class type, while () => T is.
|
4

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.

1 Comment

This is a very good thing to know about Streams in general, but here this is not the culprit: 1/0 is evaluated before calling Stream.apply, well before even instantianting the Stream.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.