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のいずれかが間違っています(他の言語ではまだ試していません)。だから私の質問は、「ループ内で変数を閉じることの「正しい」セマンティクスは何ですか?」です。

編集:私はこれを「修正」する方法を尋ねていません。ループ内にローカル変数を追加し、その変数を閉じて、c#/JavaScriptでluaの動作を取得できることはわかっています。ループ変数を閉じることの理論的に正しい意味は何であるか、そしてどの言語がそれぞれの方法でクロージャを実装するかについての短いリストのボーナスポイントを知りたいです。

編集:私の質問を言い換えると、「ラムダ計算でループ変数を閉じる動作は何ですか?」

4

4 に答える 4

5

Lua のマニュアルでは、これが機能する理由を正確に説明しています。これは、while ループの観点からインデックス for ループを次のように記述します。

 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

于 2013-01-15T04:14:36.910 に答える
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 にはブロック スコープはなく、関数スコープのみです。言語が違うだけで、やり方も違う。

于 2013-01-15T04:09:53.113 に答える
2

「ラムダ計算でループ変数を閉じる動作はどうなりますか?」

ラムダ計算にはループ変数はありません。

于 2013-01-15T16:57:09.600 に答える
1

ループ変数を閉じることは、他の変数を閉じることと同じです。問題は、言語固有のループ構造と、それらがループ変数をループの内側または外側に置くコードに変換されるかどうかにあります。

たとえばwhile、C#、Lua、または JavaScript でループを使用すると、3 つの言語すべてで結果は同じ (10) になります。for(;;)JavaScript または C#のループについても同様です(Lua では使用できません)。

ただし、JavaScript でループを使用すると、各クロージャーが(output: )for (i in x)の新しいコピーを取得することがわかります。Lua とC#についても同様です。繰り返しますが、これは、これらの言語がループを構築する方法と、ループ変数の値をループの本体に公開する方法に関係しており、クロージャー セマンティクスの違いではありません。i0 1 2 3 ...for i=x,yforeach

実際、C# の場合foreach、この動作は 4.5 から 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>
 }

これは重大な変更であり、ループ変数を閉じるときにプログラマーの期待に応えるために行われました。クロージャのセマンティクスは変更されていません。ループ変数の位置はそうでした。

于 2013-01-15T18:08:06.633 に答える