In Lua, upvalues are variables. The reference manual refers to them as "external local variables". They aren't constants that are copied once at the "closing" of the function; they are mutable variables. The non-local variables are bound to storage locations rather than to values. A more minimal example to demonstrate this behavior would be:
local upval = 42
function f() return upval end -- upval is not copied, only referenced
upval = 33 -- the upvalue is a reference to a value...
-- ... so reassigning it modifies the upvalue `f` operates with:
print(f()) -- 33
In your example, the functions returned inside foo
by makeAdder
all share the same withWhat
upvalue, so modifying it changes the upvalue the adders operate with. To explicitly copy at the time of "closing" f1
and f2
, assign to a local
variable inside makeAdder
:
function makeAdder()
local withWhat = withWhat
return function(a)
return a + withWhat
end
end
Side notes: Lua does not suffer the problem of Go et al. that loop variables are common to all anonymous functions in the loop. That is
local fs = {}
for i = 1, 3 do fs[i] = function() print(i) end end
for i = 1, 3 do fs[i]() end
will print 1
to 3
as expected (rather than printing 3
as using local i = 1; while i <= 3 do fs[i] = function() print(i) end; i = i + 1 end
would), since for var = ... do ... end
(and also for
-in
) creates a local variable scope inside the loop. In practice you can even modify the loop variable without affecting the loop (though this is not guaranteed).
The fact that upvalues are mutable and not copied at closure creation is very useful; without it, corecursion would require other means of indirection, such as global/environmental variables:
local bar -- "forward declaration" of function bar as upvalue to foo
function foo()
print("foo")
return bar()
end
-- This has to change the upvalue `foo` uses for the corecursion to work.
function bar()
print("bar")
return foo()
end
foo() -- start infinite alternating printing of foo and bar
besides, it allows making a simple, inefficient form of OOP where all methods can treat the common upvalues as a set of shared "instance variables":
function newFooBar()
local foo = "foo"
local bar = "bar"
return {
toggle = function() foo, bar = bar, foo end,
print = function() print(foo, bar) end,
}
end
local foobar = newFooBar()
foobar.print() -- foo bar
foobar.toggle()
foobar.print() -- bar foo