Подтвердить что ты не робот

Как работает управление памятью замыканий в Scala?

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. Я только что прочитал главу о закрытии "Программирование в Scala", и в этом вопросе есть только сказать (стр. 155):

Компилятор Scala переупорядочивает вещи в таких случаях, чтобы захваченный параметр находился в куче вместо стека и, таким образом, мог пережить вызов метода, который его создал. Эта перестановка все позаботится автоматически, поэтому вам не нужно беспокоиться об этом.

Может ли кто-нибудь объяснить, как это работает на уровне байтового кода? Является ли доступ похожим на переменную-член класса со всеми связанными с ней последствиями синхронизации и производительности?

4b9b3361

Ответ 1

Вы можете использовать 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 (не используемые в закрытии) являются обычными языковыми переменными.

Ссылка на эту оболочку хранится как поле в экземпляре функции.

lambdalift в -Xprint:lambdalift является фазой . Вы можете получить все фазы с помощью -Xshow-phases. Вы можете использовать номер фазы вместо имени, это полезно, когда вы не уверены, какой этап вам нужен.