Как обсуждалось в блоге Эрика Липперта Закрытие переменной цикла, считающейся вредной , закрытие переменной цикла в C # может иметь неожиданные последствия. Я пытался понять, применима ли та же самая «гоча» к Scala .
Прежде всего, поскольку это вопрос Scala, я попытаюсь объяснить пример Эрика Липперта на C #, добавив несколько комментариев к его коду
// 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());
Большинство людей ожидают, что эта программа напечатает 100, 110, 120. На самом деле она печатает 120, 120, 120.
Проблема в том, что функция () => v
, которую мы добавляем в список funcs
, закрывается по переменной v , а не по значению v . Когда v изменяет значение, в первом цикле все три замыкания, которые мы добавляем в список funcs
, «видят» одну и ту же переменную v, которая (к тому времени, когда мы применяем их во втором цикле) имеет значение 120 для всех них .
Я пытался перевести пример кода в 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?
Действительно ли Scala не страдает от той же проблемы, или я только что плохо перевел код Эрика Липперта и не смог его воспроизвести?
Такое поведение сбило с толку многих доблестных разработчиков на C #, поэтому я хотел убедиться, что в Scala нет странных подобных ошибок. Но также, как только вы поймете, почему C # ведет себя так, как он это делает, вывод примера кода Эрика Липперта имеет смысл (в основном, это то, как работают замыкания): так что же делает Scala по-другому?