Using Scala by-name parameters
I am studying the book "Functional Programming in Scala" and came across an example that I do not 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 am having is the smart constructor ( cons
) where it calls the constructor for the case class Cons
. The specific syntax used to pass values head
and tail
does not make sense to me. Why not just call the constructor like so:
Cons(head, tail)
As I understand it, the syntax used causes two Function0 objects to be created that simply return values head
and tail
. How is this different from a simple pass head
and tail
(no prefix () =>
) since the case class is Cons
already defined to accept these parameters by name anyway? Isn't that redundant? Or am I missing something?
source to share
The difference is => A
not equal() => A
The former is passing by name and the latter is a function that takes no parameters and returns A.
You can check this in the Scala REPL.
scala> def test(x: => Int): () => Int = x
<console>:9: error: type mismatch;
found : Int
required: () => Int
def test(x: => Int): () => Int = x
^
A simple reference to x
in my example results in a parameter call. In your example, this is creating a method that defers calling x.
source to share
First, you assume that => A
both are the () => A
same. However, it is not. For example, it => A
can be used only in the context of passing parameters by name - it is impossible to declare a val
type => A
. Since parameters are case class
always val
(unless s is explicitly declared var
), it's understandable why it case class Cons[+A](h: => A, t: => Stream[A])
doesn't work.
Secondly, simply transferring the by-name parameter to a function with an empty parameter list is not the same as what the above code does: using lazy val
s, it is ensured that hd
, as well as being tl
evaluated, no more than once . If the code is readable
Cons(() => hd, () => tl)
the original one hd
will be evaluated every time the method (t214) of the h
object's method is Cons
called. Usage lazy val
, hd
is evaluated only the first time a method of h
this object is called Cons
, and the same value is returned in each subsequent call.
Demonstrating the stripped-down style difference in the REPL:
> def foo = { println("evaluating foo"); "foo" }
> val direct : () => String = () => foo
> direct()
evaluating foo
res6: String = foo
> direct()
evaluating foo
res7: String = foo
> val lzy : () => String = { lazy val v = foo; () => v }
> lzy()
evaluating foo
res8: String = foo
> lzy()
res9: String = foo
Note that there is no result of "evaluating foo" in the second call lzy()
, unlike in the second call direct()
.
source to share
Note that the method parameters cons
are named parameters ( hd
and tl
). This means that if you call cons
, no arguments will be evaluated until called cons
; they will be evaluated later, the moment you use them internally cons
.
Note that the constructor cons
serves two functions of the type Unit => A
, but not as name parameters. Therefore, they will be evaluated before calling the constructor.
If you do Cons(head, tail)
, then head
u will be evaluated tail
, which means hd
u will be evaluated tl
.
But the whole point is not to name hd
and tl
until needed (when someone turns to h
or t
to an object cons
). So, you are passing two anonymous functions to the constructor cons
; these functions will not be called until someone calls h
or t
.
source to share