16

Scala は次のようなクロージャを許可します

def newCounter = {
  var a=0
  () => {a+=1;a}
}

これは、呼び出しごとに から始まる新しい独立したカウンター関数を返す関数を定義します1

scala> val counter1 = newCounter
counter1: () => Int = <function0>

scala> counter1()
res0: Int = 1

scala> counter1()
res1: Int = 2

scala> val counter2 = newCounter
counter2: () => Int = <function0>

scala> counter2()
res2: Int = 1

scala> counter1()
res3: Int = 3

これは通常a、newCounter のスタック フレームのメモリ アドレスを表すため、非常に印象的です。「Programming in Scala」のクロージャーの章を読んだところですが、その点について次のことしか述べていません (p. 155):

このような場合、Scala コンパイラーは、キャプチャーされたパラメーターがスタックではなくヒープ上に存在するように再配置します。この再配置はすべて自動的に処理されるため、心配する必要はありません。

これがバイトコードレベルでどのように機能するかを詳しく説明できる人はいますか? アクセスは、関連するすべての同期とパフォーマンスへの影響を伴うクラスのメンバー変数に似ていますか?

4

1 に答える 1

16

scalac -Xprint:lambdalift <scala-file-name>これを調査するために使用できます。

あなたのコードは実際には次のようなものです:

def newCounter = {
  val a: runtime.IntRef = new runtime.IntRef(0);
  new Function0 {
    private[this] val a$1 = a
    def apply() = {
      a$1.elem = a$1.elem + 1
      a$1.elem
    }
  }
}

varラムダで使用されるすべてのラッパーがあります。その他vars(クロージャーでは使用されない) は、一般的なロケール変数です。

このラッパーへのリンクは、関数のインスタンスにフィールドとして格納されます。

lambdaliftin-Xprint:lambdaliftコンパイラ フェーズです。ですべてのフェーズを取得できます-Xshow-phases。名前の代わりにフェーズ番号を使用できます。必要なフェーズがわからない場合に役立ちます。

于 2013-06-16T14:32:35.343 に答える