名前渡しパラメータ
基本
引数の定義時に「=>」をつけると名前渡しパラメータになる。どのようなものかは以下を参照
object Sample { def foo = { println("foo") 123 } /** 値渡し */ def byValue(a: Int) = { println("byValue entry") val r = a + a println("byValue exit") r } /** 名前渡し */ def byName(a: => Int) = { // =>をつけてるだけ println("byName entry") val r = a + a println("byName exit") r } def main(args: Array[String]) { println(byValue(foo)) println("") println(byName(foo)) } }
この実行結果は
foo byValue entry byValue exit 246 byName entry foo foo byName exit 246
両方ともfooを引数として渡しているが、
- 値渡しの場合は、評価された後の値が渡されており、その時にfooが一度だけ呼び出されている。
- 名前渡しの場合は、渡されるときには評価されず、必要なときに何度もfooが呼び出されている。
関数渡しとは異なる(らしい)
先のbyNameの定義を、明示的な「関数渡し」とすると、こうなる。
def byName(a:() => Int) = { println("byName entry") val r = a() + a() // 評価時にカッコが必要 println("byName exit") r }
そしてbyName呼び出し側もこう書く必要がある。
println(byName(() => foo))
※あるいは、「def foo」を「def foo()」にすればよい。
しかし、呼び出す関数に引数を与えたい場合にはこの書き方が必須である。つまり、
object Sample { def double(value: Int): Int = value * 2 def higher(f: (Int) => Int): Int = { f(123) } def main(args: Array[String]) { println(higher(double)) } }
結果は
246
名前渡しの利用例1
一般に名前渡しは「新たな制御構造」を作成する場合に便利に利用できるとのことであるが、以下のように「ブロックを実行して、その値を返すか、例外が起こったときには一律に例外処理をする」といった用途にも使用できる。
object Sample { // 不適当だが呼び出したいメソッドたち。おそらくレガシーコード ///////////// def notZero(value: Int): Int = { println("notZero called") if (value != 0) value else throw new NullPointerException("ERROR") // とても不適当な例外 } def nothing() { // このメソッドはUnitを返す println("nothing called") } // 公開したいメソッドたち。おそらく新しいAPI /////////////////////// def something(value: Int): Int = processThrowable { notZero(value) } def anything() = processThrowable { nothing } // レガシーコードの結果値を返すか、あるいは例外を適切に変換する //////// private def processThrowable[T](block: => T): T = { try { block } catch { // 不適当な例外を変更する case re: NullPointerException => { throw new IllegalArgumentException(re.getMessage()) } } } def main(args: Array[String]) { println(something(12)) try { something(0) } catch { case ia: IllegalArgumentException => { println("exception:" + ia.getMessage()) } } anything() } }
実行結果は
notZero called 12 notZero called exception:ERROR nothing called
名前渡しの利用例2
ロギングに使う。
trait Logging { def log(message: => String) { if (true) { // どこかのフラグを見る println(message) } } } object LogTest extends Logging { def main(args: Array[String]) { log("num args:" + args.length) if (args.length > 0) { log("first arg:" + args(0)) } } }
Javaの場合は常に値渡しなので、上記のように記述すると、logメソッドの引数は必ず評価されてしまうため、 次のようにしないと、不必要な評価が発生してしまう。
if (isLogEnabled()) { log("message " ...) }
Scalaの場合、本当にログが必要なときにだけ評価が行われる。
参考1:http://stackoverflow.com/questions/2018528/logging-in-scala
参考2:http://stackoverflow.com/questions/978252/logging-in-scala