パーサー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
に割り当てることで、何かが起こっているようです。
これまでのところ、これが何をするのか、なぜそれが有用なのかを理解することができませんでした。誰か助けてもらえますか?
名前による呼び出しの引数は要求するたびにと呼ばれます。レイジーvalは初回と呼ばれ、値が保存されます。もう一度要求すると、保存された値を取得します。
したがって、次のようなパターン
def foo(x: => Expensive) = {
lazy val cache = x
/* do lots of stuff with cache */
}
は、可能な限り長く、かつ一度だけ実行できる究極の延期作業パターンです。コードパスがx
をまったく必要としない場合、評価されません。複数回必要な場合は、1回だけ評価され、将来の使用のために保存されます。したがって、保証されたゼロ(可能な場合)または1(そうでない場合)のいずれかの時間のかかる高価な呼び出しを行います。
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
が評価されます。ただし、p
はval
であるため、パラメーターq
はonceと結果は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