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

pyt*_*ude 37 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("~")
}
Run Code Online (Sandbox Code Playgroud)

显然,这里有一些事情正在q为lazy val 分配call-by-name参数p.

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

Rex*_*err 90

每次请求时都会调用call-by-name参数.第一次调用lazy vals 然后存储该值.如果再次要求它,您将获得存储的价值.

因此,一个模式就像

def foo(x: => Expensive) = {
  lazy val cache = x
  /* do lots of stuff with cache */
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1,我原以为按名称调用在某种程度上等于懒惰,但事实并非如此!感谢您的澄清... (2认同)

Fra*_*ank 25

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)
    ...
Run Code Online (Sandbox Code Playgroud)

如您所见,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
Run Code Online (Sandbox Code Playgroud)