2013年3月2日土曜日

[Scala] 無名関数の return の仕組み(その2)

前回の記事 に引き続き、無名関数中の return 文の仕組みについて調べてみる。
今回は 匿名関数を引数に引き渡して深い階層からリターンしてくる場合を見てみよう。
今回の Scala コードはこれ。

  def main(args:Array[String]) {
    println("main start")
    test();
    println("main finish")
  }

  private def test() {
    val f = () => return;
    println("test start")
    test2(f);  
    println("test finish")   // 実行されない。
  }
 
  def test2(f: () => Unit) {
    println("test2 start")
    f();
    println("test2 finish")  // 実行されない。
  }

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

test2() 内の 匿名関数実行で test2()、test() を一気に脱出している。



デコンパイル結果

デコンパイルしてみる。

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

  private 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");
      test2(f);
      Predef..MODULE$.println("test finish");
    }
    catch (NonLocalReturnControl localNonLocalReturnControl) {
      if (localNonLocalReturnControl.key() != localObject) break label59;
    }
    ((BoxedUnit)localNonLocalReturnControl.value()); 
    return; 
    label59: throw localNonLocalReturnControl;
  }

  public void test2(Function0 f) {
    Predef..MODULE$.println("test2 start");
    f.apply$mcV$sp();
    Predef..MODULE$.println("test2 finish");
  }

相変わらず Java コードとしてはいまいちおかしい。このあたりは Java Decompiler の限界か。

このコードでも 匿名関数内の return 文が NonLocalReturnControl のスローになっているため、 test2()、test() を一気に脱出する様子がわかる。
test2() 内の f() の実行で NonLocalReturnControl がスローされ、test() の末尾でキャッチして test() をリターンしている。

また、NonLocalReturnControl をキャッチするコードは、匿名関数定義側にのみ存在し、 匿名関数を実行する test2() には出現せずに普通の関数実行コードになっている。



try...catch でくくる

前回やってみたように、匿名関数内の return を try...catch でくくってみよう。

  def main(args:Array[String]) {
    println("main start")
    testx();
    println("main finish")
  }

  private def testx() {
    val f = () => 
      try return 
      catch { case t:Throwable => println("catched: " + t)}
    println("testx start")
    test2(f);  // test2 を抜けるはずだった・・・
    println("testx finish") // 実行されないはずだった・・・
  }
 
  def test2(f: () => Unit) {
    println("test2 start")
    f();
    println("test2 finish")
  }

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

10行目で NonLocalReturnControl がキャッチされた結果、 test2 及び testx の脱出が行われなくなっていることがわかる。

test2 内で NonLocalReturnControl をキャッチした場合にも同様なことが発生する。
特に 関数を実行する test2 では匿名関数であることがわからないので、 うっかりすると予期しない動作になってしまうので気を付けないといけない。



0 件のコメント:

コメントを投稿