斯卡拉的懒惰论点:它们如何运作?

时间:2012-03-21 16:59:16

标签: scala lazy-evaluation

在来自解析器组合器库的文件Parsers.scala(Scala 2.9.1)中,我似乎遇到了一个鲜为人知的Scala功能,称为“懒惰参数”。这是一个例子:

def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q // lazy argument
  (for(a <- this; b <- p) yield new ~(a,b)).named("~")
}

显然,这里发生了一些事情,即将名称q分配给懒惰的p

到目前为止,我还没有弄清楚它的作用以及为什么它有用。有人可以帮忙吗?

2 个答案:

答案 0 :(得分:82)

每次请求时都会将名称调用参数称为 。 Lazy vals第一次被称为 ,然后存储该值。如果你再次要求它,你将获得储值。

因此,像

这样的模式
def foo(x: => Expensive) = {
  lazy val cache = x
  /* do lots of stuff with cache */
}

是最终的推迟工作 - 尽可能长而且只做一次的模式。如果您的代码路径根本不需要x,那么它永远不会被评估。如果您需要多次,它只会被评估一次并存储以备将来使用。所以你做了昂贵的电话,无论是零(如果可能)还是一次(如果没有),保证。

答案 1 :(得分:22)

Scala的维基百科文章甚至回答了lazy关键字的作用:

  

使用关键字lazy推迟值的初始化,直到使用此值。

此外,此代码示例中包含q : => Parser[U]的内容是call-by-name参数。以这种方式声明的参数仍然没有评估,直到您在方法中的某处显式评估它。

以下是scala REPL中有关call-by-name参数如何工作的示例:

scala> def f(p: => Int, eval : Boolean) = if (eval) println(p)
f: (p: => Int, eval: Boolean)Unit

scala> f(3, true)
3

scala> f(3/0, false)

scala> f(3/0, true)
java.lang.ArithmeticException: / by zero
    at $anonfun$1.apply$mcI$sp(<console>:9)
    ...

如您所见,3/0在第二次通话中根本没有得到评估。将惰性值与上面的call-by-name参数组合起来会产生以下含义:调用方法时不会立即计算参数q。相反,它被分配给惰性值p,它也不会立即进行评估。只有在使用p时才会导致q的评估。但是,由于pval,参数q只会被评估一次,结果会存储在p中以供日后重复使用循环。

您可以在repl中轻松看到,否则可能会发生多重评估:

scala> def g(p: => Int) = println(p + p)
g: (p: => Int)Unit

scala> def calc = { println("evaluating") ; 10 }
calc: Int

scala> g(calc)
evaluating
evaluating
20