web-dev-qa-db-ja.com

Scalaの遅延引数:それらはどのように機能しますか?

パーサーCombinatorsライブラリのファイルParsers.scala(Scala 2.9.1)で、あまり知られていないScala "lazy arguments"と呼ばれる機能に遭遇したようです。以下に例を示します:

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を遅延val pに割り当てることで、何かが起こっているようです。

これまでのところ、これが何をするのか、なぜそれが有用なのかを理解することができませんでした。誰か助けてもらえますか?

37
python dude

名前による呼び出しの引数は要求するたびにと呼ばれます。レイジーvalは初回と呼ばれ、値が保存されます。もう一度要求すると、保存された値を取得します。

したがって、次のようなパターン

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

は、可能な限り長く、かつ一度だけ実行できる究極の延期作業パターンです。コードパスがxをまったく必要としない場合、評価されません。複数回必要な場合は、1回だけ評価され、将来の使用のために保存されます。したがって、保証されたゼロ(可能な場合)または1(そうでない場合)のいずれかの時間のかかる高価な呼び出しを行います。

91
Rex Kerr

Scala に関するウィキペディアの記事では、lazyキーワードの機能についても回答しています。

キーワードlazyを使用すると、この値が使用されるまで値の初期化が延期されます。

さらに、このコードサンプルにq : => Parser[U]は名前による呼び出しパラメーターです。この方法で宣言されたパラメーターは、メソッドのどこかで明示的に評価するまで、未評価のままです。

以下は、名前による呼び出しパラメータの機能に関する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$1.apply$mcI$sp(<console>:9)
    ...

ご覧のとおり、3/0は2回目の呼び出しではまったく評価されません。上記のように遅延値を名前による呼び出しパラメーターと組み合わせると、次の意味になります。パラメーターqは、メソッドの呼び出し時にすぐに評価されません。代わりに、遅延値pに割り当てられますが、これもすぐには評価されません。後でのみ、pを使用すると、qが評価されます。ただし、pvalであるため、パラメーターqonceと結果は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
27
Frank