18

私は現在、さまざまな言語でのクロージャーの実装を検討しています。ただし、Scalaに関しては、クロージャがJavaオブジェクトにどのようにマップされるかについてのドキュメントを見つけることができません。

Scala関数がFunctionNオブジェクトにマップされていることは十分に文書化されています。クロージャーの自由変数への参照は、その関数オブジェクトのどこかに格納する必要があると思います(たとえば、C ++ 0xで行われるように)。

また、scalacを使用して以下をコンパイルしてから、JDを使用してクラスファイルを逆コンパイルしてみました。

object ClosureExample extends Application { 
  def addN(n: Int) = (a: Int) => a + n
  var add5 = addN(5)
  println(add5(20))
}

逆コンパイルされたソースには、Function1の匿名のサブタイプがあります。これは私のクロージャーであるはずです。ただし、apply()メソッドは空であり、匿名クラスにはフィールドがありません(クロージャー変数を格納する可能性があります)。デコンパイラーはクラスファイルから興味深い部分を取り出すことができなかったと思います...

今質問に:

  • 変換が正確にどのように行われるか知っていますか?
  • それがどこに文書化されているか知っていますか?
  • 私が謎を解く方法について別の考えがありますか?
4

2 に答える 2

31

一連の例を分解して、それらがどのように異なるかを確認しましょう。(RC1を使用している場合は、でコンパイルし-no-specializationて、理解しやすくします。)

class Close {
  var n = 5
  def method(i: Int) = i+n
  def function = (i: Int) => i+5
  def closure = (i: Int) => i+n
  def mixed(m: Int) = (i: Int) => i+m
}

まず、何をするのか見てみましょうmethod

public int method(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   invokevirtual   #17; //Method n:()I
   5:   iadd
   6:   ireturn

かなり簡単です。それは方法です。パラメータをロードし、getterを呼び出してn、add、returnします。Javaのように見えます。

どうfunctionですか?実際にはデータを閉じません、無名関数(と呼ばれClose$$anonfun$function$1ます)です。特殊化を無視する場合、コンストラクターと適用が最も重要です。

public scala.Function1 function();
  Code:
   0:   new #34; //class Close$$anonfun$function$1
   3:   dup
   4:   aload_0
   5:   invokespecial   #35; //Method Close$$anonfun$function$1."<init>":(LClose;)V
   8:   areturn

public Close$$anonfun$function$1(Close);
  Code:
   0:   aload_0
   1:   invokespecial   #43; //Method scala/runtime/AbstractFunction1."<init>":()V
   4:   return

public final java.lang.Object apply(java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokestatic    #26; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
   5:   invokevirtual   #28; //Method apply:(I)I
   8:   invokestatic    #32; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
   11:  areturn

public final int apply(int);
  Code:
   0:   iload_1
   1:   iconst_5
   2:   iadd
   3:   ireturn

したがって、「this」ポインタをロードし、それを囲むクラスを引数として取る新しいオブジェクトを作成します。これは、実際には、すべての内部クラスの標準です。この関数は外部クラスに対して何もする必要がないため、スーパーのコンストラクターを呼び出すだけです。次に、applyを呼び出すときに、ボックス/ボックス解除のトリックを実行してから、実際の数学を呼び出します。つまり、5を追加するだけです。

しかし、Close内で変数のクロージャーを使用するとどうなりますか?セットアップはまったく同じですが、コンストラクターClose$$anonfun$closure$1は次のようになります。

public Close$$anonfun$closure$1(Close);
  Code:
   0:   aload_1
   1:   ifnonnull   12
   4:   new #48; //class java/lang/NullPointerException
   7:   dup
   8:   invokespecial   #50; //Method java/lang/NullPointerException."<init>":()V
   11:  athrow
   12:  aload_0
   13:  aload_1
   14:  putfield    #18; //Field $outer:LClose;
   17:  aload_0
   18:  invokespecial   #53; //Method scala/runtime/AbstractFunction1."<init>":()V
   21:  return

つまり、入力がnullでない(つまり、外部クラスがnullでない)ことを確認し、フィールドに保存します。ボクシング/アンボクシングラッパーの後で、それを適用するときが来たら:

public final int apply(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   getfield    #18; //Field $outer:LClose;
   5:   invokevirtual   #24; //Method Close.n:()I
   8:   iadd
   9:   ireturn

そのフィールドを使用して親クラスを参照し、のゲッターを呼び出すことがわかりますn。追加、返却、完了。したがって、クロージャは非常に簡単です。無名関数コンストラクタは、囲んでいるクラスをプライベートフィールドに保存するだけです。

では、内部変数ではなくメソッド引数閉じるとどうなるでしょうか。それが何をするかClose$$anonfun$mixed$1です。まず、mixedメソッドの機能を確認します。

public scala.Function1 mixed(int);
  Code:
   0:   new #39; //class Close$$anonfun$mixed$1
   3:   dup
   4:   aload_0
   5:   iload_1
   6:   invokespecial   #42; //Method Close$$anonfun$mixed$1."<init>":(LClose;I)V
   9:   areturn

mコンストラクターを呼び出す前にパラメーターをロードします!したがって、コンストラクターが次のようになっているのは当然です。

public Close$$anonfun$mixed$1(Close, int);
  Code:
   0:   aload_0
   1:   iload_2
   2:   putfield    #18; //Field m$1:I
   5:   aload_0
   6:   invokespecial   #43; //Method scala/runtime/AbstractFunction1."<init>":()V
   9:   return

そのパラメータはプライベートフィールドに保存されます。外部クラスへの参照は必要ないため、保持されません。そして、あなたはどちらかを適用することによって驚くべきではありません:

public final int apply(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   getfield    #18; //Field m$1:I
   5:   iadd
   6:   ireturn

はい、その保存されたフィールドをロードして計算を行います。

あなたの例でこれを見ないためにあなたが何をしていたのかわかりません-オブジェクトには両方MyObjectMyObject$クラスがあり、メソッドは直感的ではないかもしれない方法で2つに分割されるため、オブジェクトは少しトリッキーです。しかし、applyは間違いなく適用され、システム全体は期待どおりに機能します(座って、非常に長い間非常に難しいことを考えた後)。

于 2010-04-19T20:53:15.327 に答える
5

疑似クロージャであり、環境に閉じられているように見える変数を変更できないJavaの匿名内部クラスとは異なり、Scalaのクロージャは実際のものであるため、クロージャコードは周囲の環境の値を直接参照します。このような値は、これを可能にするためにクロージャーから参照される場合、異なる方法でコンパイルされます(メソッドコードが現在のアクティベーションフレーム以外のアクティベーションフレームからローカルにアクセスする方法がないため)。

対照的に、Javaでは、それらの値は内部クラスのフィールドにコピーされます。そのため、言語では、囲んでいる環境の元の値がである必要があるfinalため、分岐することはありません。

囲んでいる環境の値に対するScala関数リテラル/クロージャのすべての参照は、関数リテラルのメソッドのコード内にあるため、関数リテラル用に生成された実際のサブクラスapply()のフィールドとしては表示されません。Function

逆コンパイルの方法はわかりませんが、逆コンパイルの詳細は、apply()メソッドの本体にコードが表示されない理由を説明している可能性があります。

于 2010-04-19T20:28:28.737 に答える