名前渡しパラメータ
基本
引数の定義時に「=>」をつけると名前渡しパラメータになる。どのようなものかは以下を参照
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
