Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference in Closures between Javascript and Lua

Why is it that these two seemingly identical pieces of code behave differently in Javascript and Lua?

Lua:

function main()
    local printFunctions={}
    local i,j
    for i=1,10 do
        local printi = function()
            print(i)
        end
        printFunctions[i]=printi
    end
    for j=1,10 do
        printFunctions[j]()
    end
end
main()

Javascript:

function main()
{
    var printFunctions=[]
    var i,j;
    for(i=0;i<10;i++)
    {
        var printi = function()
        {
            console.log(i);
        }
        printFunctions[i]=printi;
    }
    for(j=0;j<10;j++)
    {
        printFunctions[j]();
    }
}
main()

The example in Lua prints 0 1 2 3 4 5 6 7 8 9, but the example in Javascript prints 10 10 10 10 10 10 10 10 10 10. Can anybody explain the difference between closures in Javascript and Lua that causes this to happen? I'm coming from a Javascript background, so please focus on the Lua side.

I attempted to explain this on my blog, but I'm not sure if my explanation is correct, so any clarification would be appreciated.

EDIT

Thanks everyone, now I understand. This slightly modified version of the Lua code prints 10,10,10,10,10,10,10,10,10,10 as expected

function main()
    local printFunctions={}
    local i,j,k
    for i=1,10 do
        k=i
        local printi = function()
            print(k)
        end
        printFunctions[i]=printi
    end
    for j=1,10 do
        printFunctions[j]()
    end
end

main()
like image 668
Flight Odyssey Avatar asked Oct 07 '13 23:10

Flight Odyssey


1 Answers

It's as simple as the following:

Lua local variables are scoped only to the nearest do-end block, while JavaScript variables declared with var are scoped to the nearest function boundaries. Closures get over this point by placing them in their own individual scope in a function, solving the problem of scope.

About your question regarding local i, j being in the outer scope, the for statement in Lua creates the scope of the counter being used in the block scope, even if there is a variable declaration in the outer scope. The documentation says (reference):

The loop variable v is local to the loop; you cannot use its value after the for ends or is broken. If you need this value, assign it to another variable before breaking or exiting the loop.

This means that the var initialisation is made local to the for loop scope, so placing local i, j in the outer scope has no effect. This can be seen in an equivalent to Lua's for statement, given in the documentation:

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

However, JavaScript's for statement varies significantly (reference):

IterationStatement : for ( var VariableDeclarationListNoIn ; Expressionopt ; Expressionopt ) Statement

Because the declaration of the for loop is the same as any ordinary variable declaration, it's equivalent to placing it outside the loop, much different to how the Lua for loop works. The next version of ECMAScript (ES6) plans to introduce the let keyword, which in a for loop will have a similar meaning to how Lua's for loop works:

for (let i = 0; i < 10; ++i) setTimeout(function () { console.log(i); }, 9); // 0,1,2,3,4,5,6,7,8,9
for (var i = 0; i < 10; ++i) setTimeout(function () { console.log(i); }, 9); // 10,10,10,10,10,10,10,10,10,10
like image 59
Qantas 94 Heavy Avatar answered Sep 24 '22 14:09

Qantas 94 Heavy