Posted by
| Nick Gammon
Australia (23,133 posts) Bio
Forum Administrator |
Message
| Lua has the default behaviour that all variables are global unless declared local to a block.
Here is a method of reversing that behaviour. For example, PHP has the reverse behaviour, that all variables in a function are local unless you specify otherwise.
First, let's look at the problem. If you write a small function like this, and don't bother with 'local' in front of variables, then all variables you use "pollute" the global address space, and potentially conflict with variables used by other functions.
function test (x)
a = 1
b = 2
c = x * 2
print (c)
end -- test
test (5) --> 10
print ("after=", a, b, c) --> after= 1 2 10
You can see from this that the variables a, b and c have been changed in global address space.
We can hide the global space from the function with setfenv, like this:
function test (x)
setfenv (1, {}) --> change environment
a = 1
b = 2
c = x * 2
print (c) --> attempt to call global 'print' (a nil value)
end -- test
test (5)
print ("after=", a, b, c)
However we have been too successful here, the function "print" is also hidden. One way around this is to effectively do what PHP does, and force you to declare any global functions you want. You have to do this before the setfenv call ...
function test (x)
local print = print
setfenv (1, {})
a = 1
b = 2
c = x * 2
print (c)
end -- test
test (5) --> 10
print ("after=", a, b, c) --> after= nil nil nil
The line "local print = print" makes a copy of the global variable "print" into a local variable. Now we can print. We could also copy a whole table, like this:
local string = string --> get all string functions
Another approach is to use a metatable to allow us access to global variables for reading, but not for changing. This gives us the advantage of letting us use all existing functions and variables, but we still can't change anything inadvertently:
function test (x)
local M = {}
setmetatable (M, {__index = _G} )
setfenv (1, M)
a = 1
b = 2
c = x * 2
print (c)
end -- test
test (5) --> 10
print ("after=", a, b, c) --> after= nil nil nil
The earlier approach has the advantage that you explicitly state which global variables you are planning to use, the later approach saves a bit of work.
Another technique again is to force the declaration of variables. This is a useful method, and is used in many programming languages (eg. C). It is useful because it catches inadvertent misspellings of variables.
function test (x)
local M = {}
local mt = {
__index = _G,
__newindex = function (t, v)
error ("variable not declared: '" .. v .."'", 2)
end
} -- end metatable
setmetatable (M, mt)
setfenv (1, M)
a = 1 --> table_test.lua:13: variable not declared: 'a'
b = 2
c = x * 2
print (c)
end -- test
test (5)
print ("after=", a, b, c)
Now an attempt to write to the variable 'a' raises an error, forcing us to make sure it is declared:
function test (x)
local M = {}
local mt = {
__index = _G,
__newindex = function (t, v)
error ("variable not declared: '" .. v .."'", 2)
end
} -- end metatable
setmetatable (M, mt)
setfenv (1, M)
local a, b, c
a = 1
b = 2
c = x * 2
print (c)
end -- test
test (5) --> 10
print ("after=", a, b, c) --> after= nil nil nil
However this version still has a problem - we detect changes to "a" but not attempts to read from it (eg. print (a) ). Another change will fix that:
function test (x)
local print = print
local M = {}
local undec = function (t, v)
error ("variable not declared: '" .. v .."'", 2)
end
local mt = {
__index = undec,
__newindex = undec
} -- end metatable
setmetatable (M, mt)
setfenv (1, M)
print (a) --> table_test.lua:16: variable not declared: 'a'
a = 1
b = 2
c = x * 2
print (c)
end -- test
test (5)
print ("after=", a, b, c)
I had to put back the line "local print = print" because the new metamethod for __index now throws an error, so we have to explicitly declare which global variables we want.
function test (x)
local print = print
local M = {}
local undec = function (t, v)
error ("variable not declared: '" .. v .."'", 2)
end
local mt = {
__index = undec,
__newindex = undec
} -- end metatable
setmetatable (M, mt)
setfenv (1, M)
local a, b, c
print (a)
a = 1
b = 2
c = x * 2
print (c)
end -- test
This version now works - we have declared a, b, and c.
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|