Zhiltsoff Igor
Zhiltsoff Igor

Reputation: 1822

Why are Scala's LazyList's elements displayed as unevaluated after being computed?

I am completely new to Scala. I've been playing around with LazyLists. Consider the following:

val fun: Int => Int = (x: Int) => {
    println("PROCESSING...")
    x + 1
}

val lazyList = LazyList(fun(1), fun(2), fun(3))

The snippet above prints "PROCESSING..." thrice, which indicates that all three elements of LazyList were computed. I found such behaviour to be rather unexpected for a lazy collection. So, I decided to print it:

println(lazyList) // which prints "LazyList(<not computed>)".

I thought it would print out LazytList(2, 3, 4). (I'm not completely sure, but it seems to me that Scala's println works for lazy collections sort of like the :sprint command in GHCi, dividing the collection in two parts: the evaluated and the unevaluated one.)

So, here are my questions, concerning this code:

  1. Why are no elements displayed as evaluated? If they are indeed unevaluated, what was this triple "PROCESSING..." thing about? If not, why does println claim so?
  2. Why do we want LazyList's arguments like fun(1) to be computed right away? Why do we cast away the call-by-need strategy when initializing? Are there any other cases where such a thing happens? Note that no output is produced when we use map instead of writing this down manually, as expected.

Upvotes: 4

Views: 1111

Answers (3)

Tomer Shetah
Tomer Shetah

Reputation: 8529

I think you are mixing 2 issues. The computation of the elements, and printing them. Take for example an easier case where you have:

val lazyList = LazyList(1, 2, 3)

The output of:

println(lazyList)

will be:

LazyList(<not computed>)

although the values are clearly computed.

When printing lazy list, we are actually calling LazyList.toString which calls addStringNoForce. As we can see, as long as the state is not defined, we get the standard LazyList(<not computed>). In order to get the state to be true, we must cause a call to the member state. There are several ways to do that. On way for example is to call head. Please note that this will cause the first element to be materialized, and not the rest of the lazy list.

For example. the code:

val lazyList = LazyList(1, 2, 3)
lazyList.head
println(lazyList)

will output:

LazyList(1, <not computed>)

and the following:

val lazyList = LazyList(1, 2, 3)
lazyList.tail.head
println(lazyList)

will output:

LazyList(1, 2, <not computed>)

The reason you saw the PROCESSING... output is not related to LazyList at all. If you call fun(1), without the LazyList, you get the same.

Upvotes: 3

Alec
Alec

Reputation: 32309

Instead of using LazyList.apply, any of the following work (without evaluating their arguments):

  • LazyList.tabulate(3)(fun)
  • fun(1) #:: fun(2) #:: fun(3) #:: LazyList.empty
  • LazyList.range(1, 4).map(fun)
  1. Why do we want LazyList's arguments like fun(1) to be computed right away? Why do we cast away the call-by-need strategy when initializing? Are there any other cases where such a thing happens? Note that no output is produced when we use map instead of writing this down manually, as expected.

I don't think it is desirable that fun(1) be computed right away, but it follows from the fact that you used LazyList.apply to construct your list. LazyList(fun(1), fun(2), fun(3)) is syntactic sugar for LazyList.apply(fun(1), fun(2), fun(3)) and the type signature for that function is def apply[A](elems: A*): LazyList[A]. Note that the arguments of that function are not call by name.

So: why is LazyList.apply not defined with call-by-name arguments?

  1. it is impossible in Scala to define a variadic function with by-name arguments
  2. def apply actually comes from SeqFactory which is mixed into most collection companion objects. The LazyList companion object might be the one place in the collections library where the companion object apply isn't a helpful addition.
  1. Why are no elements displayed as evaluated? If they are indeed unevaluated, what was this triple "PROCESSING..." thing about? If not, why does println claim so?

LazyList only knows when elements are evaluated because you forced them through accessing them in LazyList. In the case of using LazyList.apply, the following happens:

  1. the arguments are evaluated when LazyList.apply is initially called
  2. LazyList.apply calls LazyList.from, which is going to create a LazyList by lazily iterating through the intermediate Seq materialized in step 1

By the time step 2 has finished, the LazyList doesn't know that its contents are evaluated. Furthermore, the spine of the list is itself unevaluated.

:sprint in GHCi isn't a great comparison because it is much more omniscient in its understanding of when things are evaluated. It does this by crawling the runtime heap and printing _ when it runs across thunks. By comparison, println just calls out to LazyList#toString, which is a regular Scala method.

Upvotes: 4

Mario Galic
Mario Galic

Reputation: 48420

Try with #:: constructor

scala> fun(1) #:: fun(2) #:: LazyList.empty
val res0: scala.collection.immutable.LazyList[Int] = LazyList(<not computed>)

#:: takes by-name argument unlike LazyList.apply which takes by-value.

Upvotes: 4

Related Questions