简体   繁体   中英

Scala lazy val explanation

I am taking the Functional Programming in Scala course on Coursera and I am having a hard time understanding this code snippet -

def sqrtStream(x: Double): Stream[Double] = {
  def improve(guess: Double): Double = (guess+ x/ guess) / 2
  lazy val guesses: Stream[Double] = 1 #:: (guesses map improve)
  guesses
}

This method would find 10 approximate square root of 4 in increasing order of accuracy when I would do sqrtSteam(4).take(10).toList.

Can someone explain the evaluation strategy of guesses here? My doubt is what value of guesses in substituted when the second value of guesses is picked up?

Let's start from simplified example:

 scala> lazy val a: Int  = a + 5
 a: Int = <lazy>

 scala> a
 stack overflow here, because of infinite recursion

So a is recalculating til it gets some stable value, like here:

scala> def f(f:() => Any) = 0 //takes function with captured a - returns constant 0
f: (f: () => Any)Int

scala> lazy val a: Int  = f(() => a) + 5
a: Int = <lazy>

scala> a
res4: Int = 5 // 0 + 5

You may replace def f(f:() => Any) = 0 with def f(f: => Any) = 0 , so a definition will look like it's really passed to the f: lazy val a: Int = f(a) + 5 .

Streams use same mechanism - guesses map improve will be passed as parameter called by name (and lambda linked to the lazy a will be saved inside Stream, but not calculated until tail is requested), so it's like lazy val guesses = #::(1, () => guesses map improve) . When you call guessess.head - tail will not be evaluated; guesses.tail will lazily return Stream (improve(1), ?) , guesses.tail.tail will be Stream(improve(improve(1)), ?) and so on.

You can easily find out what's going on by modifying the map function, as described in the scaladoc example :

scala> def sqrtStream(x: Double): Stream[Double] = {
     |   def improve(guess: Double): Double = (guess + x / guess) / 2
     |   lazy val guesses: Stream[Double] = 1 #:: (guesses map {n =>
     |     println(n, improve(n))
     |     improve(n)
     |   })
     |   guesses
     | }
sqrtStream: (x: Double)Stream[Double]

The output is:

scala> sqrtStream(4).take(10).toList
(1.0,2.5)
(2.5,2.05)
(2.05,2.000609756097561)
(2.000609756097561,2.0000000929222947)
(2.0000000929222947,2.000000000000002)
(2.000000000000002,2.0)
(2.0,2.0)
(2.0,2.0)
(2.0,2.0)
res0: List[Double] = List(1.0, 2.5, 2.05, 2.000609756097561, 2.0000000929222947, 2.000000000000002, 2.0, 2.0, 2.0, 2.0)

The value of guesses is not substituted. A stream is like a list, but its elements are evaluated only when they are needed and then they stored, so next time you access them the evaluation will not be necessary. The reference to the stream itself does not change.

On top of the example Αλεχει wrote, there is a nice explanation in Scala API: http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Stream

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM