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?
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 and returns an A.
You can test this out 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
^
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.
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 a val
of type => A
. As case class
parameters are always val
s (unless explicitly declared var
s), it is clear why case 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 both hd
and tl
are evaluated at most once. If the code read
Cons(() => hd, () => tl)
the original hd
would be evaluated every time the h
method (field) of a Cons
object is invoked. Using a lazy val
, hd
is evaluated only the first time the h
method of this Cons
object is invoked, and the same value is returned in every subsequent invocation.
Demonstrating the difference in a stripped-down fashion 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 how the "evaluating foo" output in the second invocation of lzy()
is gone, as opposed to the second invocation of direct()
.
Note that the parameters of the method cons
are by-name parameters (hd
and tl
). That means that if you call cons
, the arguments will not be evaluated before you call cons
; they will be evaluated later, at the moment you use them inside cons
.
Note that the Cons
constructor takes two functions of type Unit => A
, but not as by-name parameters. So these will be evaluated before you call the constructor.
If you do Cons(head, tail)
then head
and tail
will be evaluated, which means hd
and tl
will be evaluated.
But the whole point here was to avoid calling hd
and tl
until necessary (when someone accesses h
or t
in the Cons
object). So, you pass two anonymous functions to the Cons
constructor; these functions will not be called until someone accesses h
or t
.
来源:https://stackoverflow.com/questions/26663219/use-of-scala-by-name-parameters