Upload page content

You can upload content for the page named below. If you change the page name, you can also upload content for another page. If the page name is empty, we derive the page name from the file name.

File to load page content from
Page name
Comment

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