Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.
like image 893
Zhiltsoff Igor Avatar asked Dec 30 '22 14:12

Zhiltsoff Igor


2 Answers

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.

like image 146
Alec Avatar answered Apr 12 '23 22:04

Alec


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.

like image 38
Mario Galic Avatar answered Apr 13 '23 00:04

Mario Galic