2013-01-15 3 views
1

Рассмотрим следующий код Lua:Какова правильная семантика замыкания по переменной цикла?

f = {} 

for i = 1, 10 do 
    f[i] = function() 
     print(i .. " ") 
    end 
end 

for k = 1, 10 do 
    f[k]() 
end 

Это печатает числа от 1 до 10. В этом случае i закрыта над значением для каждой итерации внешнего цикла. Это, как я всегда понимаемые затворы, и я был очень рад ...

... пока я не портирования некоторого Lua кода на C#, и я попытался сделать то же самое:

var f = new Action[10]; 

for (int i = 0; i < 10; i++) 
{ 
    f[i] = (new Action(delegate() 
    { 
     Console.Write(i + " "); 
    })); 
} 
for (int k = 0; k < 10; k++) 
{ 
    f[k](); 
} 

И теперь я получаю номер 10, напечатанный 10 раз (давайте забудем, что lua массивы основаны на 1). На самом деле происходит то, что в этом случае замыкание работает над переменной, а не ее значением, что имеет большой смысл, так как я вызываю функции только после завершения первого цикла.

JavaScript, кажется, имеет ту же семантику (близко над переменной):

var f = [] 

for (var i = 0; i < 10; i++) 
{ 
    f[i] = function() 
    { 
     document.write(i + ' '); 
    }; 
} 

for (var k = 0; k < 10; k++) 
{ 
    f[k](); 
} 

На самом деле, оба поведения делают много смысла, но, конечно, несовместимы.

Если есть «правильный» способ сделать это, то либо lua, либо C# и JavaScript ошибочны (я еще не пробовал с другими языками). Поэтому мой вопрос: «Какова« правильная »семантика закрытия переменной внутри цикла?»

Редактировать: Я не спрашиваю, как «исправить» это. Я знаю, что я могу добавить локальную переменную внутри цикла и закрыть ее, чтобы получить поведение lua в C#/JavaScript. Я хочу знать, что является теоретически правильным значением закрытия над зацикленной переменной, и бонусные баллы для краткого списка того, какие языки реализуют замыкания по-своему.

Редактировать: Чтобы перефразировать мой вопрос: «Каково поведение закрытия зацикленной переменной в исчислении лямбда?»

+1

Возможный дубликат [Почему плохо использовать переменную итерации в выражении лямбда] (http://stackoverflow.com/questions/227820/why-is-it-bad-to-use-a-iteration- variable-in-a-lambda-expression) –

+0

JavaScript не имеет области действия блока, вам понадобится дополнительная функция. – Bergi

+3

Почему вы думаете, что есть «правильный» способ? Это разные языки; ни один из них не является «неправильным». Они просто разные. –

ответ

5

Руководство Lua объясняет, почему это работает. Он описывает индекс для цикла с точки зрения цикла в то время как это:

for v = e1, e2, e3 do block end 

--Is equivalent to: 

do 
    local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3) 
    if not (var and limit and step) then error() end 
    while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do 
    local v = var 
    block 
    var = var + step 
    end 
end 

Обратите внимание, как переменная цикла v объявлена ​​внутри объем петли while. Это делается специально для того, чтобы точно, что вы делаете.

+0

Я видел детали этой реализации, но я не знал, что это было сделано специально, чтобы это разрешить. У вас есть конкретная цитата? –

+0

Нашел его в разделе «Программирование в Луа» 7.2. Оказывается, что lua не закрывает значения, он просто создает новые специально для generic для construct. Тривиально показать, как lua ​​ведет себя так же, как C#/JavaScript, переписывая его как 'while'. Таким образом, правильный способ заключается в том, чтобы закрыть переменную, а не значение. –

+0

C# 5 изменяет 'foreach', чтобы переместить' v' из-за пределов цикла внутрь цикла, по той же причине. – Mud

3

Нет «правильного» способа. Существуют разные способы. В C#, вы бы это исправить, сделав переменную области видимости цикла:

for (int i = 0; i < 10; i++) 
{ 
    int j = i; 

    f[i] = (new Action(delegate() 
    { 
     Console.Write(j + " "); 
    })); 
} 

В JavaScript можно добавить объем, делая и вызов анонимной функции:

for (var i = 0; i < 10; i++) { 
    (function(i) { 
     f[i] = function() { 
      document.write(i + ' '); 
     }; 
    })(i); 
} 

итерационные переменные в C# не имеют области цикла. JavaScript не имеет области блока, а просто области функций. Это просто разные языки, и они делают что-то по-другому.

+0

Я знаю, как «исправить» это, но это был не мой вопрос. Вы говорите, что нет «правильного» способа, но почти все в CS формально определено, поэтому я считаю, что теоретически правильный путь должен быть. –

+1

@ パ ン ダ パ ジ ャ マ: Не так далеко, насколько я знаю (или не буду спорить). Информатика не может формально определить, как должен вести себя каждый язык, потому что все они разные. – Ryan

+0

хорошо, так что же такое поведение в лямбда-исчислении? –

2

«Каково поведение закрытия зацикленной переменной в исчислении лямбда?»

В лямбда-исчислении нет переменных цикла.

1

Закрытие переменной цикла подобно закрытию любой другой переменной. Проблема связана с конкретными языковыми конструкциями циклов и преобразуются ли они в код, который помещает переменную цикла внутри или вне цикла.

Например, если вы используете цикл while на C#, Lua или JavaScript, результат на всех трех языках одинаковый (10). То же для цикла for(;;) в JavaScript или C# (недоступно в Lua).

Однако, если вы используете цикл for (i in x) в JavaScript, вы обнаружите, что каждое закрытие получает новую копию i (выход: 0 1 2 3 ...). То же для for i=x,y в Lua и foreach в C#. Опять же, это связано с тем, как эти языки конструируют эти циклы и как они выставляют значение переменной цикла в тело цикла, не разница в семантике закрытия.

Фактически, в случае C# foreach такое поведение changed from 4.5 to 5. Эта конструкция:

foreach (var x in l) { <loop body> } 

Используется для перевода в (псевдокод):

E e = l.GetEnumerator() 
V v 
while (e.MoveNext()) { 
     v = e.Current 
     <loop body> 
} 

В C# 5, это было изменено на:

E e = l.GetEnumerator() 
while (e.MoveNext()) { 
     V v = e.Current 
     <loop body> 
} 

Это критическое изменение, сделать лучше отвечают ожиданиям программистов при закрытии переменной цикла. Семантика закрытия не изменилась; положение переменной цикла.

Смежные вопросы