lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

If at the top of my Lua program, I put:

local math = require"mathx"

then I have burnt my bridges. I can never again access the built-in
'math' as a global variable. I can still get it as '_G.math', but no
matter how deeply do blocks and functions are nested, 'math' will
either be this upvalue or a newer local variable shadowing it.

Not too bad, in the case of 'math': actually Luiz's 'mathx' is in many
ways nicer if your C compiler can compile it.

But what about other names? (Acknowledgment: the following observation
was pointed out on this list some time ago by Egor Skriptunoff.) In
the object-oriented programming idiom of putting _ENV as first
parameter of a function, this persistent property of an upvalue is a
potential source of hard-to-find bugs.


> rotate = function (_ENV,c,s) return {x=c*x-s*y,y=s*x+c*y} end
> z = rotate({x=0.8,y=0.6},0.8,0.6); print(z.x,z.y)
0.28    0.96


> local x=1; rotate = function (_ENV,c,s) return {x=c*x-s*y,y=s*x+c*y} end
> z = rotate({x=0.8,y=0.6},0.8,0.6); print(z.x,z.y)
0.44    1.08

The bug is obvious, here in the interpreter. You can see the offending
upvalue x.

But in a program, if 200 lines earlier, in an innocent-looking bit of
code that should have been enclosed in do ... end, we had

local x = 1
for _,v in ipairs(t) do x = x*v end
print("The product is: ",x)

that x would still have remained visible, and the same bug would be
present. Not so easy to find anymore.

There is a workaround. There always is. Wrap the body in 'load()'.

> local x=1; rotate = load"local _ENV,c,s = ...; return {x=c*x-s*y,y=s*x+c*y}"
> z = rotate({x=0.8,y=0.6},0.8,0.6); print(z.x,z.y)
0.28    0.96

It's not even many more keystrokes. But it does not do the same thing.
You'll need extra code to check that 'load' succeeded; runtime error
messages from inside are harder to understand.

I'm not too sure how one could implement hiding of upvalues at the
language level. (At the implementation level, it's obvious. Just skip
the phase that looks for them.) Maybe a keyword that makes upvalues
invisible to the end of the current local scope.

    rotate = function (_ENV,c,s) blind
      return {x=c*x-s*y,y=s*x+c*y}