This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
ElvishJerricco's profile picture

Lua Function Environments

Started by ElvishJerricco, 25 June 2013 - 01:52 PM
ElvishJerricco #1
Posted 25 June 2013 - 03:52 PM
One of Lua's most powerful features is setting function environments. Environments are tables that hold the global values that a function references.


function a()
	c = 3
end
a()
print(c)

The function a sets the global variable c to 3. Also, the declaration of the a function sets the global a to that function. The result of this program is printing the number 3.

So what table is holding these a and c keys? That's known as the environment! For programs run from the shell, the environment is a shared one across all programs run by the shell. So if you set a global in one program, the environment holds onto it, and any other program can access it.

But how do we set our own environments? There's a nice little function called setfenv.


local function a()
	c = 4
end
local env = {}
setfenv(a, env)
a()
print(env.c)

a gets its environment set to env, so when c is defined globally, it is stored in env. So now we can keep track of the globals declared by a function. But this leaves us with a problem. Now a can't access global functions. This is easily solved with metatables


local function a()
	print("test")
end
local env = setmetatable({}, {__index=_G})
setfenv(a, env)
a()

And now we can get pretty clever with setting and getting variables. If we wanted to, we could create a setter/getter system that could be very useful.


local setters = {}
local _width
function setters.setWidth(w)
	-- We can not only set the local _width value, but also do some logic
	_width = w
	redrawScreen()
end

local env = setmetatable({}, {__index = getfenv(), __newindex=function(t,k,v)	-- FYI, getfenv has various functions
										-- one of which is to get the current environment
	local setter = setters["set" .. k:sub(1,1):upper .. k:sub(2)]
	if type(setter) == "function" then
		setter(v)
	end
end})

local function f()
	width = 5 -- now setting this width key automatically calls a redraw routine!
end
setfenv(f, env)
f()

In writing that, I almost made a mistake that should be noted. I accidentally called the local _width variable width. This would have been a problem. f() would be referencing a local variable, instead of setting a global, and the environment __newindex metamethod never would have been called.

So hopefully this shed some light on the idea of globals, that seems so mysterious to so many people. Happy coding!
Engineer #2
Posted 30 June 2013 - 10:37 AM
Thanks, this really helped me understanding environments. Now I see how they can be useful.
But, Im a tad confused this part:

if type(setter) ~= "function" then
        setter(v)
end

Im hoping it that should be this (Otherwise Im confused):

if type(v) ~= "function" then
    setter(v)
end
or:

if type(setter) == "function" then
    setter(v)
end

Other then that, a good tutorial :)/>
Dlcruz129 #3
Posted 30 June 2013 - 11:31 AM
Good job!
Bordo_Bereli51 #4
Posted 04 July 2013 - 02:11 PM
great job
ElvishJerricco #5
Posted 04 July 2013 - 11:58 PM
Thanks, this really helped me understanding environments. Now I see how they can be useful.
But, Im a tad confused this part:

if type(setter) ~= "function" then
		setter(v)
end

Im hoping it that should be this (Otherwise Im confused):

if type(v) ~= "function" then
	setter(v)
end
or:

if type(setter) == "function" then
	setter(v)
end

Other then that, a good tutorial :)/>

Oh hey what do you know. Thanks. Fixed.
lieudusty #6
Posted 05 July 2013 - 05:20 PM
Great tutorial! :)/>
cdel #7
Posted 13 October 2014 - 10:28 AM
Just to Clarify: If I were to load an api within a program environment, would another program be able to access the loaded api's or are they environment specific?
ElvishJerricco #8
Posted 13 October 2014 - 02:37 PM
Just to Clarify: If I were to load an api within a program environment, would another program be able to access the loaded api's or are they environment specific?

Depends on the way you do it.
EDIT: Unless you just mean somewhere within a function that's had its environment set, you call os.loadAPI. In that case, yea that will have the same result as normally calling os.loadAPI, unless the environment the function is in overrides the os table with a custom one that has a custom loadAPI function.
Edited on 13 October 2014 - 02:09 PM