问题
I'm following a book's example to implement a Steam class using lazy evaluation in Scala.
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)
}
def empty[A]: Stream[A] = Empty
def apply[A](as: A*): Stream[A] = {
if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*))
}
}
Then I used a simple function to test if it's working
def printAndReturn: Int = {
println("called")
1
}
Then I construct Stream like the following:
println(s"apply: ${
Stream(
printAndReturn,
printAndReturn,
printAndReturn,
printAndReturn
)
}")
The output is like this:
called
called
called
called
apply: Cons(fpinscala.datastructures.Stream$$$Lambda$7/1170794006@e580929,fpinscala.datastructures.Stream$$$Lambda$8/1289479439@4c203ea1)
Then I constructed Stream using cons
:
println(s"cons: ${
cons(
printAndReturn,
cons(
printAndReturn,
cons(printAndReturn, Empty)
)
)
}")
The output is:
cons: Cons(fpinscala.datastructures.Stream$$$Lambda$7/1170794006@2133c8f8,fpinscala.datastructures.Stream$$$Lambda$8/1289479439@43a25848)
So here are two questions:
- When constructing Stream using the apply function, all
printAndReturn
are evaluated. Is this because the recursive call toapply(as.head, ...)
evaluates every head? - If the answer to the first question is true, then how do I change
apply
to make it not force evaluation?
回答1:
No. If you put a breakpoint on the
println
you'll find that the method is actually being called when you first create theStream
. The lineStream(printAndReturn, ...
actually calls your method however many times you put it there. Why? Consider the type signatures forcons
andapply
:def cons[A](hd: => A, tl: => Stream[A]): Stream[A]
vs:
def apply[A](as: A*): Stream[A]
Note that the definition for
cons
has its parameters marked as=> A
. This is a by-name parameter. Declaring an input like this makes it lazy, delaying its evaluation until it is actually used. Hence yourprintln
will never get called usingcons
. Compare this toapply
. You're not using a by name parameter and therefore anything that gets passed in to that method will automatically get evaluated.Unfortunately there isn't a super easy way as of now. What you really want is something like
def apply[A](as: (=>A)*): Stream[A]
but unfortunately Scala does not support vararg by name parameters. See this answer for a few ideas on how to get around this. One way is to just wrap your function calls when creating the Stream:Stream( () => printAndReturn, () => printAndReturn, () => printAndReturn, () => printAndReturn)
Which will then delay the evaluation.
回答2:
When you called
Stream(
printAndReturn,
printAndReturn,
printAndReturn,
printAndReturn
)
the apply in the companion object was invoked. Looking at the parameter type of the apply, you would notice that it is strict. So the arguments will be evaluated first before being assigned to as. What as becomes is an Array of Ints
For 2, you can define apply as
def apply[A](as: (() => A)*): Stream[A] =
if (as.isEmpty) empty else cons(as.head(), apply(as.tail: _*))
and as was suggested above, you need to pass the arguments as thunks themselves as in
println(s"apply: ${Stream(
() => printAndReturn,
() => printAndReturn,
() => printAndReturn,
() => printAndReturn
)}")
来源:https://stackoverflow.com/questions/45258360/scala-lazy-evaluation-and-apply-function