2013年2月2日土曜日

[Scala] 名前渡し

Scala で関数を定義する際、仮引数名と型名の間に 「 => 」を記載すると名前渡しとなり、値が必要になったときに式の値が評価される。
  var x = 1;
  
  def main(args: Array[String]) {
    x = 1;  
    notdelayed(x*2);
    x = 1;  
    delayed(x*2);
  }

  def notdelayed(value : Int) {
    x = 100;
    println("notdelayed: " + value)
  }  

  def delayed(value : => Int) {
    x = 100;
    println("delayed: " + value)
    x = 200;
    println("delayed: " + value)
  }

--------------------
notdelayed: 2
delayed: 200
delayed: 400

notdelayed() では 関数呼び出しの時点で値が確定するため関数の中で x の値を変換させても 引数値に影響はない。この形式を「値渡し」と呼ぶ。

一方で delayed() は 「名前渡し」になっているので引数に式(x*2) が渡されていて、関数の中で x の値を変化させると引数値が影響を受ける。
この時、「最初に評価された時点で値が確定する」のではなくて、「値が必要なたびに評価される」。delayed 関数の中で 2回 value の値を取得しているがそのたびに 式 (x*2) が評価されている。

ちなみに lazy 変数による遅延評価は、名前渡しとは異なって最初の評価時に値が確定し、確定後の変数の参照では値は再評価されない。


名前渡しとFunction0


仮引数を名前渡しにする 「(value : => Int) 」 は引数のない関数(Function0)を引数にとるようにも見えるが、Function0 を引数にするときには、「(value : () => Int) 」のように 「 () 」が必要。
...
    val f0 = () => 100;
    f0arg(f0);
...
  def f0arg(value : () => Int) {
    println(value())
  }

--------------------
100



ローカル変数の引き渡しと更新操作


先ほどの例では メンバー変数を含む式を名前渡ししていたが、(式を評価する場所では不可視になるはずの)ローカル変数を含むこともできる。
  def main(args: Array[String]) {
    var xx = 10;
    delayed(xx = xx * 2);
    println("xx = " + xx);
  }
  private def delayed(value : => Unit) {
    value; value;
  }
--------------------
xx = 40

この例では xx を含む式を名前渡ししているが、この xx は main() のローカル変数であり、delayed() からは不可視であるにも関わらす式を評価できている。
また、ここで渡した式 「 xx = xx * 2 」 は代入式であるため、 delayed 呼び出しの結果として xx の値が変化する。delayed() では value を2回参照しているため 「 xx = xx * 2 」 が実行されて結果が4倍になっている。

名前渡しでは実行先で見えない変数も使えるので、変数へのポインタのようなものに挿げ替えて実行先に式が渡されているような、そんな動きをしているように見える。

このように、名前渡しを使うと自スコープ内で閉じているはずの変数値が他所から参照/更新できる。 式を評価するタイミングや回数は呼び出し先の自由であり、かつ、名前渡しか値渡しかを決定するのも呼び出し先の定義によるため、注意して使わないと予期しない結果になる可能性がある。

0 件のコメント:

コメントを投稿