With Statement |
|
In several object oriented languages a with statement is implemented.
The structure like "with ... as" has a simple solution with a temporary variable, when one assigns the scope to the temporary variable and uses it whenever is needed.
The more complicated case, when "with" is used in order to extend the scope implicitly, like in the following example:
with (obj) {
some_method();
}
Lua doesn't provide such a structure by design. A simple solution is presented on this page.
A basic library in lua provides enough instruments to implement something like "with" statement.
There's two functions to operate with environment: setfenv() and getfenv(), as well as a table _G exists.
"with" structure in common sense extends a scope with the provided object. It is possible with metadata manipulations (see getmetatable() and setmetatable()).
Lets see how to get such a thing in a Lua.
The subject structure can be implemented with the following function:
function with(env) local oldenv = getfenv(2); setfenv(2, env); return function() setfenv(2, oldenv) end; end;
The return value is a function to restore the initial environment. Then, an outline of the structure will be:
local endwith = with (env)
...
any_method();
...
endwith();
The main drawback of the method here is that we have no access to a variables from the initial scope. There're two simple ways to overcome the problem.
A slightly modified function:
function with(env) local oldenv = getfenv(2); setfenv(2, env); return function() setfenv(2, oldenv) end, _G; end;
Now the global scope is available:
local endwith, _G = with (env) ... any_method(); ... _G.print("a function from a global scope"); ... endwith();
Another solution extends a specified scope with a _G:
function with(env) local oldenv = getfenv(2); local mt = getmetatable(env) or {}; mt.__index = _G; setmetatable(env, mt); setfenv(2, env); return function() setfenv(2, oldenv) end, _G; end;
Here the second return value may be omitted.
A global scope is available implicitly, like in othe languages:
local endwith = with (env) ... any_method(); ... print("a function from a global scope"); ... endwith();
And a final test code:
-- tiny environment with the only function Test = { output = function() print("\tTest.output()") end }; -- function for environment test function output() print("Top-level output()") end; -- the tricky with function function with(env) local oldenv = getfenv(2); local mt = getmetatable(env) or {}; mt.__index = _G; setmetatable(env, mt); setfenv(2, env); return function() setfenv(2, oldenv) end, _G; end; function main() output(); --[[ *** local function output() print("*** the substituted function!"); end; --]] local endwith, _G = with(Test); --[[ global environment still in _G table ]] _G.print("\texplicit print() invocation"); --[[ implicit invocation ]] print("\timplicit print() invocation"); --[[ call output here ]] output(); endwith(); --[[ environment restored outside of "with" ]] output(); end; main();
You can uncomment the function marked with "***" for fun. It reveals a limitation, that one must keep in mind.
--IgorBogomazov?
LuaFiveTwo replaces getfenv and setfenv with _ENV, allowing with to be implemented as follows.
function with(...) local envs = {...} local f = (type(envs[#envs]) == 'function') and table.remove(envs) local env if #envs == 1 then env = envs[1] else local mt = {} function mt.__index(t, k) for i=1,#envs do local v = rawget(envs[i], k) if v ~= nil then return v end end end env = setmetatable({}, mt) end if f then return f(env) else return env end end -- test local function print2(...) print('printing', ...) end print 'one' with({print=print2}, _ENV, function(_ENV) print('two', math.sqrt(4)) end) print 'three' do local _ENV = with({print=print2}, _ENV) print('four', math.sqrt(4)) end print 'five'