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

Как Scala поддерживает значения переменной при определении закрытия?

Поддерживает ли scala значения переменной по копии или ссылке?

Например, в Ruby "закрытие фактически продлит время жизни всех переменных, которые ему нужны. Оно не будет копировать их, но сохранит ссылку на них, и сами переменные не будут иметь права на сбор мусора (если язык содержит сбор мусора), а закрытие вокруг". [Скоркин]

4b9b3361

Ответ 1

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

class Cell(var x: Int)
var c = new Cell(1)

val f1 = () => c.x /* Create a closure that uses c */

def foo(e: Cell) = () => e.x
  /* foo is a closure generator with its own scope */

val f2 = foo(c)    /* Create another closure that uses c */

val d = c          /* Alias c as d */
c = new Cell(10)   /* Let c point to a new object */
d.x = d.x + 1      /* Increase d.x (i.e., the former c.x) */

println(f1())      /* Prints 10 */
println(f2())      /* Prints 2 */

Я не могу комментировать сборку мусора, но я предполагаю, что сборщик мусора JVM не будет удалять объекты, на которые ссылается закрытие, если закрытие все еще ссылается.

Ответ 2

У jvm нет замыканий, у него есть только объект. Компилятор scala генерирует анонимные классы, реализующие соответствующий признак функции (в зависимости от аргумента и типа результата подписи) для каждого появления замыкания в коде.

Например, если для некоторого l : List[Int] вы пишете l.map(i => i + 1), он будет преобразован в

class SomeFreshName extends Function[Int, Int] {
  def apply(i: Int) = i + 1
}

l.map(new SomeFreshName())

В этом случае нет никакого действительного замыкания, как в я = > я + 1, нет свободной переменной, а только аргумент я и константа.

Если вы закрываете некоторые локальные переменные или, что то же самое, параметр функции, они должны быть переданы как параметр конструктора в класс-замыкание:

для l.map(i => s + i), где s - строковый параметр или локальный метод, он будет делать

class SomeFreshName(s: String) extends Function[Int, String] {
  def apply(i: Int) = s + i
}
l.map(new SomeFreshName(s))

передавая как можно больше параметров в конструкторе.

Примечание. Если s было полем класса вместо локального метода, тогда s + i будет фактически this.s + i, а this будет передано анонимному классу.

В сборщике мусора нет ничего особенного (опять же, jvm не знает замыкания), просто, поскольку объект замыкания имеет ссылку на s, s будет жить как минимум до тех пор, пока объект закрытия.

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

В java это разрешено только в том случае, если локальные значения final, что эквивалентно scala val, а не var.

В самом деле, с этой реализацией, как только создается замыкание, у нее есть своя копия переменной, которая закрывает другую. Если он изменяет их, эта модификация не будет отражена в методе. Если они изменены в закрытии, это не будет отражено в методе.

Предположим, вы пишете

var i = 0
l.foreach{a => println(i + ": " + a); i = i + 1}
println("There are " + i + " elements in the list")

Реализация, описанная ранее, будет

class SomeFreshName(var i: Int) extends Int => Unit {
   def apply(a: Int) = println(i + ": " + a); i = i + 1
}
var i = 0
l.foreach(new SomeFreshName(i)
println("There are " + i + " elements in the list") 

Таким образом, было бы две переменные i, одна из которых была бы в методе, а другая - в SomeFreshName. Только тот, который был в SomeFreshName, будет изменен, а последний println всегда будет сообщать 0 элементов.

Scala решить свою проблему, заменив var, сделанный в замыкании ссылочными объектами. Учитывая класс

class Ref[A](var content: A)

код сначала заменяется на

val iRef = new Ref[Int](0)
l.foreach{a => 
  println(iRef.content + ": " + a); 
  iRef.content += iRef.content + 1
}
println("There are " + i + " elements in the list")

Это делается, конечно, только для var, который происходит с помощью замыкания, а не для каждого var. При этом var был заменен на val, фактическое значение переменной было перемещено в кучу. Теперь закрытие можно сделать как обычно, и оно работает

class SomeFreshName(iRef: Ref[Int]) ...