I am going through the book "Functional Programming in Scala" and have run across an example that I don't fully understand.
In the chapter on strictness/laziness the authors describe the construction of Streams and have code like this:
sealed trait Stream[+A]
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]
object Stream {
def cons[A](hd: => A, tl: => Stream[A]) : Stream[A] = {
lazy val head = hd
lazy val tail = tl
Cons(() => head, () => tail)
}
...
}
The question I have is in the smart constructor (cons
) where it calls the constructor for the Cons
case class. The specific syntax being used to pass the head
and tail
vals doesn't make sense to me. Why not just call the constructor like this:
Cons(head, tail)
As I understand the syntax used it is forcing the creation of two Function0 objects that simply return the head
and tail
vals. How is that different from just passing head
and tail
(without the () =>
prefix) since the Cons
case class is already defined to take these parameters by-name anyway? Isn't this redundant? Or have I missed something?
Note that the parameters of the method
cons
are by-name parameters (hd
andtl
). That means that if you callcons
, the arguments will not be evaluated before you callcons
; they will be evaluated later, at the moment you use them insidecons
.Note that the
Cons
constructor takes two functions of typeUnit => A
, but not as by-name parameters. So these will be evaluated before you call the constructor.If you do
Cons(head, tail)
thenhead
andtail
will be evaluated, which meanshd
andtl
will be evaluated.But the whole point here was to avoid calling
hd
andtl
until necessary (when someone accessesh
ort
in theCons
object). So, you pass two anonymous functions to theCons
constructor; these functions will not be called until someone accessesh
ort
.First, you are assuming that
=> A
and() => A
are the same. However, they are not. For example, the=> A
can only be used in the context of passing parameters by-name - it is impossible to declare aval
of type=> A
. Ascase class
parameters are alwaysval
s (unless explicitly declaredvar
s), it is clear whycase class Cons[+A](h: => A, t: => Stream[A])
would not work.Second, just wrapping a by-name parameter into a function with an empty parameter list is not the same as what the code above accomplishes: using
lazy val
s, it is ensured that bothhd
andtl
are evaluated at most once. If the code readthe original
hd
would be evaluated every time theh
method (field) of aCons
object is invoked. Using alazy val
,hd
is evaluated only the first time theh
method of thisCons
object is invoked, and the same value is returned in every subsequent invocation.Demonstrating the difference in a stripped-down fashion in the REPL:
Note how the "evaluating foo" output in the second invocation of
lzy()
is gone, as opposed to the second invocation ofdirect()
.The difference is in
=> A
not being equal to() => A
.The former is pass by name, and the latter is a function that takes no parameters (the unit) and returns an A.
You can test this out in the Scala REPL.
Simply referencing
x
in my sample causes the parameter to be invoked. In your sample, it's constructing a method which defers invocation of x.