繁体   English   中英

Scala 惰性求值和应用函数

[英]Scala lazy evaluation and apply function

我正在按照一本书的示例在 Scala 中使用惰性求值来实现 Steam 类。

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: _*))
    }
}

然后我用一个简单的函数来测试它是否有效

def printAndReturn: Int = {
    println("called")
    1
}

然后我构造 Stream 如下:

    println(s"apply: ${
        Stream(
            printAndReturn,
            printAndReturn,
            printAndReturn,
            printAndReturn
        )
    }")

输出是这样的:

called
called
called
called
apply: Cons(fpinscala.datastructures.Stream$$$Lambda$7/1170794006@e580929,fpinscala.datastructures.Stream$$$Lambda$8/1289479439@4c203ea1)

然后我使用cons构造了 Stream :

println(s"cons: ${
    cons(
        printAndReturn,
        cons(
            printAndReturn,
            cons(printAndReturn, Empty)
        )
    )
}")

输出是:

cons: Cons(fpinscala.datastructures.Stream$$$Lambda$7/1170794006@2133c8f8,fpinscala.datastructures.Stream$$$Lambda$8/1289479439@43a25848)

所以这里有两个问题:

  1. 使用 apply 函数构造 Stream 时, printAndReturn所有printAndReturn求值。 这是因为对apply(as.head, ...)的递归调用会评估每个头部吗?
  2. 如果第一个问题的答案是正确的,那么我如何更改apply以使其不强制评估?
  1. 不。如果您在println上放置一个断点,您会发现该方法实际上是在您第一次创建Stream时被调用的。 Stream(printAndReturn, ...实际上会调用你的方法,不管你把它放在那里多少次。为什么?考虑consapply的类型签名:

     def cons[A](hd: => A, tl: => Stream[A]): Stream[A]

    对比:

     def apply[A](as: A*): Stream[A]

    请注意, cons的定义将其参数标记为=> A 这是一个按名称参数。 像这样声明输入会使其变得懒惰,将其评估延迟到实际使用为止。 因此,您的println永远不会被使用cons调用。 比较这个apply 您没有使用按名称参数,因此任何传入该方法的内容都将自动进行评估。

  2. 不幸的是,目前还没有一种超级简单的方法。 你真正想要的是def apply[A](as: (=>A)*): Stream[A]但不幸的是 Scala 不支持按名称参数的 vararg 。 有关如何解决此问题的一些想法,请参阅此答案 一种方法是在创建流时包装函数调用:

     Stream( () => printAndReturn, () => printAndReturn, () => printAndReturn, () => printAndReturn)

    这将延迟评估。

当你打电话

Stream(
            printAndReturn,
            printAndReturn,
            printAndReturn,
            printAndReturn
        )

调用了伴随对象中的应用程序。 查看 apply 的参数类型,您会注意到它是严格的。 因此,在分配给as之前,将首先评估参数。 什么成为是int数组

对于 2,您可以将应用定义为

def apply[A](as: (() => A)*): Stream[A] =
    if (as.isEmpty) empty else cons(as.head(), apply(as.tail: _*))

正如上面所建议的,您需要将参数作为 thunks 本身传递给

println(s"apply: ${Stream(
    () => printAndReturn,
    () => printAndReturn,
    () => printAndReturn,
    () => printAndReturn
  )}")

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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