lua-users home
lua-l archive

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


On 14/08/2007, at 2:07 AM, Tom Miles wrote:

I need to read up on environments as I don't really understand what
setfenv does.
The first thing to understand about environments is that Lua doesn't  
really have a concept of global variables. With the exception of  
variables declared "local" and upvalues, variables are looked up in  
the current environment. Effectively, the current environment is the  
"root" of the variables tree. As an example:
a = 2

Is really like doing this:

_G.a = 2

Where _G is a convenience variable created by Lua to refer to "the current environment". However you could delete _G, and the statement "a = 2" would still work:
_G = nil
a = 2

What Lua is really doing when you say "a = 2" is:

* fetch the current environment table
* assign 2 to be the value of key "a" in that table

You can see that _G and the current environment are the same thing by doing this:
--------

> print (_G)
table: 0x300350
> print (getfenv ())
table: 0x300350
>

--------

Now the powerful thing about environments is you can make "sandboxes" by changing the environment. This small example shows how you can eliminate all existing "global" variables:
--------

function f ()
  print ("hello")
end -- f

f ()   --> prints "hello"

t = {}  -- some new environment

setfenv (f, t)

f ()  --> error: attempt to call global 'print' (a nil value)

--------


After the setfenv the function f, which worked when called the first time, no longer can find the function "print".
I could add the single "global" variable "print" to my new  
environment like this:
--------

function f ()
  print ("hello")
end -- f

f ()  --> prints "hello"

t = { print = print }  -- some new environment

setfenv (f, t)

f ()  --> prints "hello"

--------

Now the environment "t" has the function "print" in it, with the value from the original environment.
An interesting extension to this idea is to make *all* of the  
original global variables available to the new environment, by using  
a metatable:
--------

function f ()
  print ("hello")
end -- f

f ()  --> prints "hello"

t = { }  -- some new environment

setmetatable (t, { __index = getfenv () })

setfenv (f, t)

f ()  --> prints "hello"

--------

This example adds a metatable to the new environment, which looks up variables which we are trying to read from, and don't exist, in the original environment. This is effectively what package.seeall does - adds read-access to the global environment, for your module.

Does your approach work with sharing data across
environments?
The "module" function call creates an environment, so the trick to  
sharing environments is to work out what you want to do exactly. The  
purpose of doing it (the module call) in the first place is to have  
local data (in the module's environment) that does not corrupt or  
affect similarly named data in other modules.
I think the problem with modules loading submodules is that the  
sharing is a level removed from the real "global" environment. A few  
debugging prints of various tables should show which module uses  
which environment.
I think you will find that if you load "utilities" early on (in your  
main state) then attempts to load it in sub-modules will find an  
already-existing utilities entry in the main environment, and  
therefore not create one in the sub-module environment. This may well  
solve your variable-sharing problem.
- Nick