Как обсуждалось в блоге Eric Lippert Закрытие переменной цикла, считающейся вредной, закрытие переменной цикла в С# может иметь неожиданные последствия. Я пытался понять, применил ли тот же "gotcha" к Scala.
Прежде всего, поскольку это вопрос Scala, я попытаюсь объяснить пример Eric Lippert С#, добавив несколько комментариев к его коду
// 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 not what we're doing and there 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 не страдает от одной и той же проблемы или я просто перевел код Эрика Липперта и не смог воспроизвести его?
Такое поведение сработало у многих доблестных разработчиков С#, поэтому я хотел убедиться, что нет никаких странных аналогичных ошибок с Scala. Но также, как только вы понимаете, почему С# ведет себя так, как это делает вывод, пример кода примера Eric Lippert имеет смысл (он работает так, как работают закрытия): так что же делает Scala по-другому?