Scala 的懒惰参数:它们是如何工作的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9809313/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:01:05  来源:igfitidea点击:

Scala's lazy arguments: How do they work?

scalalazy-evaluation

提问by python dude

In the file Parsers.scala (Scala 2.9.1) from the parser combinators library I seem to have come across a lesser known Scala feature called "lazy arguments". Here's an example:

在解析器组合器库的 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("~")
}

Apparently, there's something going on here with the assignment of the call-by-name argument qto the lazy val p.

显然,将 call-by-name 参数q分配给惰性 val 时发生了一些事情p

So far I have not been able to work out what this does and why it's useful. Can anyone help?

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

回答by Rex Kerr

Call-by-name arguments are called every time you ask for them. Lazy vals are called the first timeand then the value is stored. If you ask for it again, you'll get the stored value.

每次请求时都会调用按名称调用参数。第一次调用惰性 val ,然后存储该值。如果您再次要求,您将获得存储的价值。

Thus, a pattern like

因此,像这样的模式

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

is the ultimate put-off-work-as-long-as-possible-and-only-do-it-once pattern. If your code path never takes you to need xat all, then it will never get evaluated. If you need it multiple times, it'll only be evaluated once and stored for future use. So you do the expensive call either zero (if possible) or one (if not) times, guaranteed.

是最终推迟工作尽可能长时间并且只做一次的模式。如果您的代码路径根本不需要您x,那么它将永远不会被评估。如果您多次需要它,它只会被评估一次并存储以备将来使用。因此,您可以保证拨打昂贵的电话零次(如果可能)或一次(如果不是)。

回答by Frank

The wikipedia article for Scalaeven answers what the lazykeyword does:

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

Using the keyword lazy defers the initialization of a value until this value is used.

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

Additionally, what you have in this code sample with q : => Parser[U]is a call-by-name parameter. A parameter declared this way remains unevaluated, until you explicitly evaluate it somewhere in your method.

此外,您在此代码示例中使用的q : => Parser[U]是按名称调用参数。以这种方式声明的参数将保持未计算状态,直到您在方法中的某处显式计算它。

Here is an example from the scala REPL on how the call-by-name parameters work:

以下是 scala REPL 中关于按名称调用参数如何工作的示例:

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.apply$mcI$sp(<console>:9)
    ...

As you can see, the 3/0does not get evaluated at all in the second call. Combining the lazy value with a call-by-name parameter like above results in the following meaning: the parameter qis not evaluated immediately when calling the method. Instead it is assigned to the lazy value p, which is also not evaluated immediately. Only lateron, when pis used this leads to the evaluation of q. But, as pis a valthe parameter qwill only be evaluated onceand the result is stored in pfor later reuse in the loop.

如您所见,3/0在第二次调用中根本没有得到评估。将惰性值与像上面这样的按名称调用参数组合会产生以下含义:q调用方法时不会立即评估参数。相反,它被分配给惰性值p,它也不会立即计算。只有稍后,当p使用时这会导致对 的评估q。但是,作为p一个val参数q只被评估一次并将结果保存在p在循环以后重用。

You can easily see in the repl, that the multiple evaluation can happen otherwise:

您可以在 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