Scala lazy evaluation and apply function

两盒软妹~` 提交于 2020-05-14 09:06:08

问题


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:

  1. When constructing Stream using the apply function, all printAndReturn are evaluated. Is this because the recursive call to apply(as.head, ...) evaluates every head?
  2. If the answer to the first question is true, then how do I change apply to make it not force evaluation?

回答1:


  1. No. If you put a breakpoint on the println you'll find that the method is actually being called when you first create the Stream. The line Stream(printAndReturn, ... actually calls your method however many times you put it there. Why? Consider the type signatures for cons and apply:

    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 your println will never get called using cons. Compare this to apply. You're not using a by name parameter and therefore anything that gets passed in to that method will automatically get evaluated.

  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!