2013年3月1日金曜日

[Scala] 無名関数の return の仕組み

この前の記事で、Scala の無名関数中での return 文の動きについてみてみた。

今回はその仕組みが Java 的にどうなっているのか見てみる。 JD-Plugin の導入には前回失敗したので、デコンパイルには Java Decompiler の GUI 版を使った。
前回使ったコードはこれ。
  def main(args:Array[String]) {
    println("main start")
    test();
    println("main finish")
  }
  
  private def test() {
    val f = () => return;
    println("test start")
    f();
    println("test finish")   // 実行されない。
  }

------------------
main start
test start
main finish


デコンパイル結果

上のコードをデコンパイルすると以下のようになる。
Java コードとしてはいろいろとおかしいところもあるようなのだが、 とりあえず雰囲気だけ確認してみる。

  public void main(String[] args)
  {
    Predef..MODULE$.println("main start");
    test();
    Predef..MODULE$.println("main finish");
  }

  public void test()
  {
    Object localObject = new Object();
    try { 
      Function0 f = new AbstractFunction0() { 
        public static final long serialVersionUID = 0L;
        private final Object nonLocalReturnKey1$1;

        public final Nothing. apply() { 
          throw new NonLocalReturnControl(
              this.nonLocalReturnKey1$1, BoxedUnit.UNIT);
        }
      };
      Predef..MODULE$.println("test start");
      f.apply();
      Predef..MODULE$.println("test finish");
    } catch (NonLocalReturnControl localNonLocalReturnControl) {
      if (localNonLocalReturnControl.key() != localObject) 
          break label61;
    } 
    ((BoxedUnit)localNonLocalReturnControl.value()); 
    return; 
    label61: throw localNonLocalReturnControl;
  }

12行目から匿名関数の定義が始まる。
Java では Function0 の匿名クラスが定義されている。
この匿名関数は Scala では return の一行なのだが、 Java コードの apply() では NonLocalReturnControl の throw に置き換えられている。

出力された Java コードでは匿名関数オブジェクトの 「nonLocalReturnKey1$1」に 値をセットするコードが見当たらないので今一つ良く解らないのだが、 最後に NonLocalReturnControl をキャッチして、 自関数内で定義した匿名関数からスローされた NonLocalReturnControl なら return 、 そうでなければキャッチした NonLocalReturnControl を再スローしている。


匿名関数中の return の仕組み

まとめてみよう。

  • 匿名関数の定義時には ローカル変数の localObject を用意する。
  • 匿名関数内の return は実際には localObject を持っている NonLocalReturnControl のスローである。
  • 匿名関数を定義したら NonLocalReturnControl をキャッチして、
    自スコープで定義した localObject を持っていたら return する。
    そうでなければ 再スローする。

なるほど。匿名関数の定義元まで戻る仕組みはこうなっていたのか。


匿名関数呼び出しを try...catch でくくる

この仕組みを見ると匿名関数の呼び出しを try ... catch でくくると挙動が変わってしまう気がする。 やってみる。

  def main(args:Array[String]) {
    println("main start")
    testx();
    println("main finish")
  }
  
  private def testx() {
    val f = () => {return};
    println("testx start")
    try {
      f();   // testx() を抜けるはずだった・・・
    } catch { case t:Throwable => println("catched: " + t)}
    println("testx finish")  // catchしなければ実行されなかった・・・
  }

------------------
main start
testx start
catched: scala.runtime.NonLocalReturnControl
testx finish
main finish

f() 呼び出し後に Throwable をキャッチしたことによって、
f() 内で return が実行されても、testx() を脱出しなくなった。



匿名関数中の return 文を try...catch でくくる

匿名関数のなかで return 文を try...catch でくくるとどうなるだろう。

  def main(args:Array[String]) {
    println("main start")
    testx2();
    println("main finish")
  }
  
  private def testx2() {
    val f = () => {
      try return
      catch {case t:Throwable => println("catched: " + t)}
    };
    println("testx2 start")
    f();   
    println("testx2 finish")  // catchしなければ実行されなかった・・・
  }

------------------
main start
testx2 start
catched: scala.runtime.NonLocalReturnControl
testx2 finish
main finish

この場合も、匿名関数 f() の実行時に NonLocalReturnControl がスローされなくなった結果、 testx2 から脱出しなくなった。



匿名関数と try...catch

匿名関数呼び出しや、匿名関数内の return 文を try...catch でくくって Throwable を拾うと、 脱出の挙動が変わってしまうことが分かった。
Scala のコード上は ただの return 文で Throwable とは一見、関係なさそうに見える。
このあたりは仕組みを理解して注意深く使わないと、 Scala のコードからだけだと思いがけない結果になるので注意しないといけない。

次回 は匿名関数を他の関数に引数で引き渡しているときのコードを見てみる。



0 件のコメント:

コメントを投稿