How does Scala maintains the values of variable wh

2019-03-15 05:21发布

问题:

Does scala maintains the values of variable by copy or reference?

For example, in Ruby "the closure will actually extend the lifetime of all the variables that it needs. It will not copy them, but will retain a reference to them and the variables themselves will not be eligible for garbage collection (if the language has garbage collection) while the closure is around". [SKORKIN]

回答1:

Closures in Scala also don't deep copy objects, they'll only keep a reference to the object. Moreover, a closure does not get it's own lexical scope, but instead, it uses the surrounding lexical scope.

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 */

I can't comment on garbage collection, but I assume that the JVM's garbage collector will not remove objects that are referenced by a closure, as long as the closure is still referenced.



回答2:

The jvm has no closures, it has only object. The scala compiler generate anonymous classes implementing the appropriate Function trait (depending of the argument and result type of the signature) for each occurence of a closure in the code.

For instance, if for some l : List[Int], you write l.map(i => i + 1), it will be converted to

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

l.map(new SomeFreshName())

In this case, there is no real closure, as in i => i + 1, there is no free variable, only the argument i and constant.

If you close over some local vals, or equivalently a parameter of the function, they will have to be passed as constructor parameter to the closure-implementing class :

for l.map(i => s + i) where s is a string parameter or a local of the method, it will do

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

passing as many parameter in the constructor as needed.

Note : If s was a field of the class instead of a local of the method, then s + i would be in fact this.s + i, and this would be passed to anonymous class.

There is nothing special in the garbage collector (again, the jvm does not know of closures), simply, as the closure object has a reference to s, s will live at least as long as the closure object.

Note that exactly the same thing happens in the java language with anonymous classes. When an anonymous class uses locals of the enclosing method, those locals are silently added as fields of the anonymous classes, and passed at constructor.

In java, this is allowed only if the local are final, which is equivalent of scala val, as opposed to var.

Indeed, with this implementation, as soon as the closure is created, it has its own copy of the variable it closes other. If it modifies them, those modification will not be reflected in the method. If they are modified in the closure, this will not be reflected in the method.

Suppose you write

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

The implementation described before would be

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") 

Doing that, there would be two variable i, the one in the method, and the one in SomeFreshName. Only the one in SomeFreshName would be modified, and the last println would always report 0 elements.

Scala solve his problem by replacing var taken in the closure by reference objects. Given a class

class Ref[A](var content: A)

the code is first replaced by

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")

This is done of course only to var that happens to be taken by a closure, not to every var. Doing that, the var has been replaced by a val, the actual variable value has been moved into the heap. Now, the closure can be done as usual, and it works

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