Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closing over the loop variable in Scala

As discussed in Eric Lippert's blog post Closing over the loop variable considered harmful, closing over the loop variable in C# can have unexpected consequences. I was trying to understand if the same "gotcha" applied to Scala.

First of all, since this is a Scala question, I'll try explaining Eric Lippert's C# example adding a few comments to his code

// Create a list of integers
var values = new List<int>() { 100, 110, 120 };

// Create a mutable, empty list of functions that take no input and return an int
var funcs = new List<Func<int>>();

// For each integer in the list of integers we're trying
// to add a function to the list of functions
// that takes no input and returns that integer
// (actually that's not what we're doing and there's the gotcha).
foreach(var v in values)
  funcs.Add( ()=>v );

// Apply the functions in the list and print the returned integers.
foreach(var f in funcs)
  Console.WriteLine(f());

Most people expect this program to print 100, 110, 120. It actually prints 120, 120, 120. The issue is that the () => v function we add to the funcs list closes over the v variable, not v's value. As v changes value, in the first loop, all the three closures we add to the funcs list "see" the same variable v, which (by the time we apply them in the second loop) has value 120 for all of them.

I've tried to translate the example code to Scala:

import collection.mutable.Buffer
val values = List(100, 110, 120)
val funcs = Buffer[() => Int]()

for(v <- values) funcs += (() => v)
funcs foreach ( f => println(f()) )
// prints 100 110 120
// so Scala can close on the loop variable with no issue, or can it?

Does Scala indeed not suffer from the same issue or have I just translated Eric Lippert's code badly and have failed to reproduce it?

This behavior has tripped many a valiant C# developer, so I wanted to make sure there are no strange similar gotchas with Scala. But also, once you understand why C# behaves the way it does, the output of Eric Lippert's example code kind of makes sense (it's the way closures work, basically): so what is Scala doing differently?

like image 780
Paolo Falabella Avatar asked Mar 23 '12 16:03

Paolo Falabella


2 Answers

Scala doesn't have the same problem because v is not a var, it's a val. Therefore, when you write

() => v

the compiler understands that it is supposed to produce a function that returns that static value.

If instead you use a var, you can have the same problem. But it's a lot clearer that this is the asked-for behavior, since you explicitly create a var, and then have the function return it:

val values = Array(100, 110, 120)
val funcs = collection.mutable.Buffer[() => Int]()
var value = 0
var i = 0
while (i < values.length) {
  value = values(i)
  funcs += (() => value)
  i += 1
}
funcs foreach (f => println(f()))

(Note that if you try funcs += (() => values(i)) you will get an out of bounds exception because you have closed over the variable i which, when you call, is now 3!)

like image 128
Rex Kerr Avatar answered Oct 12 '22 13:10

Rex Kerr


The close equivalent of the C# example would be with a while loop and a var. It would behave as in C#.

On the other hand, for(v <- values) funcs += (() => v) is translated to values.foreach(v => funcs += () => v)

just to give names, that could be

def iteration(v: Int) = {funcs += () => v)
values.foreach(iteration)

The closure () => v appears in the body of iteration, and what it captures is not some var shared by all iterations, but the argument of the call to iteration, which is not shared, and moreover is a constant value rather than a variable. This prevent the unintuitive behavior.

There may well be a variable in the implementation of foreach, but it is not what the closure sees.

If in C#, you move the body of the loop in a separate method, you get the same effect.

like image 33
Didier Dupont Avatar answered Oct 12 '22 12:10

Didier Dupont