Scala懒惰评估和应用功能

时间:2017-07-22 19:26:22

标签: scala lazy-evaluation

我按照一本书的示例,在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。这是因为对apply(as.head, ...)的递归调用会对每个头进行评估吗?
  2. 如果对第一个问题的回答是正确的,那么如何更改apply以使其不进行强制评估?

2 个答案:

答案 0 :(得分:1)

  1. 没有。如果在println上放置断点,则会在第一次创建Stream时发现该方法实际被调用。行Stream(printAndReturn, ...实际上会调用您的方法,但很多时候您将它放在那里。为什么?考虑consapply的类型签名:

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

    VS

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

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

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

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

    这会延迟评估。

答案 1 :(得分:0)

致电时

rds-combined-ca-bundle.pem

伴随对象中的apply被调用。查看应用的参数类型,您会发现它很严格。因此,在将参数分配给 as 之前,将首先对其进行评估。 原样变为一个整数数组

对于2,您可以将Apply定义为

ss1: {
  ss1rootcert: fs.readFileSync("rds-combined-ca-bundle.pem").toString(), // wrong path
  sslmode: "verify-full"
}

就像上面建议的那样,您需要像

一样将参数传递为thunk本身
TIME