I am completely new to Scala. I've been playing around with LazyList
s. 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:
"PROCESSING..."
thing about? If not, why does println
claim so?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.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)
- Why do we want
LazyList
's arguments likefun(1)
to be computed right away? Why do we cast away thecall-by-need
strategy when initializing? Are there any other cases where such a thing happens? Note that no output is produced when we usemap
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?
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.
- Why are no elements displayed as evaluated? If they are indeed unevaluated, what was this triple
"PROCESSING..."
thing about? If not, why doesprintln
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:
LazyList.apply
is initially calledLazyList.apply
calls LazyList.from
, which is going to create a LazyList
by lazily iterating through the intermediate Seq
materialized in step 1By 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With