Locked History Actions

scala/ByNameParamter

名前渡しパラメータ

基本

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

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