在来自解析器组合器库的文件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
。
到目前为止,我还没有弄清楚它的作用以及为什么它有用。有人可以帮忙吗?
答案 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
的评估。但是,由于p
是val
,参数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