Create a set of interdependent Lua files without affecting global namespaces

tl; dr: What design pattern allows you to split Lua code into multiple files that need to share certain information without affecting the global table?

Background

The need for libraries to create libraries in Lua to affect the global namespace is considered a bad form:

--> somelib.lua <--
SomeLib = {... }

--> usercode.lua <--
require'somelib'
print(SomeLib) - global key created == bad

Instead, the best practice is to create a library that uses local variables, and then return them for the user to assign as needed:

< p>

--> somelib.lua <--
local SomeLib = {... }
return SomeLib

--> usercode.lua <--
local theLib = require'somelib' - consumers name lib as they wish == good

The above pattern works fine when using a single file. However, when you have multiple mutual This can become quite difficult when citing files.

Specific examples

How to rewrite the following file suite to make all the assertions pass? Ideally, rewriting will keep the same files on disk and retain responsibility for each file. (Rewriting by merging all the code into a single file is effective, but it does not help;)

< p>

--> test_usage.lua <--
require'master'

assert(MASTER.Simple)
assert(MASTER.simple)
assert(MASTER.Shared)
assert(MASTER.Shared.go1)
assert(MASTER.Shared.go2)
assert(MASTER.Simple.ref1()==MASTER .Multi1)
assert(pcall(MASTER.Simple.ref2))
assert(_G.MASTER == nil) - Does not currently pass
--> master.lua <--
MASTER = {}
require'simple'
require'multi'
require'shared1'
require'shared2'
require'shared3 '
require'reference'

--> simple.lua <--
MASTER.Simple = {}
function MASTER:simple() end

--> multi.lua <--
MASTER.Multi1 = {}
MASTER.Multi2 = {}

--> shared1.lua <- -
MASTER.Shared = {}

--> shared2.lua <--
function MASTER.Shared:go1() end

- -> shared3.lua <--
function MASTER.Shared:go2() end

--> reference.lua <--
function MASTER.Simple:ref1() return MASTER.Multi1 end
function MASTER.Simple:ref2() MASTER:simple() end

Failed: Setting the environment

I want to solve the problem by setting the environment as the main table by self-referencing. But when calling functions like require, this does not work because they will change the environment:

--> master.lua <--
foo = "original"
local MASTER = setmetatable({foo="captured"},{__index=_G})< br />MASTER.MASTER = MASTER
setfenv(1,MASTER)
require'simple'

--> simple.lua <--
print(foo ) --> "original"
MASTER.Simple = {} --> attempt to index global'MASTER' (a nil value)

You give master.lua two responsibilities:

>It defines a common module table
>It imports all submodules

Instead, you should create a separate module for (1) and import it into all submodules:

--> common.lua <--
return {}

--> master.lua <--
require'simple'
require'multi'
require'shared1'
require'shared2'
require'shared3'
require'reference'
return require'common' - return the common table

--> simple.lua <--
local MASTER = require'common' - import the common table
MASTER.Simple = {}
function MASTER :simple() end

Wait

Finally, change the first line of test_usage.lua to use local variables:

 --> test_usage.lua <--
local MASTER = require'master'
...

The test should now pass.

tl; dr: What design pattern allows you to split Lua code into multiple files that need to share certain information without affecting the global table?

Background

The need for libraries to create libraries in Lua to affect the global namespace is considered a bad form:

--> somelib.lua <--
SomeLib = {... }

--> usercode.lua <--
require'somelib'
print(SomeLib) - global key created == bad

Instead, the best practice is to create a library that uses local variables, and then return them for the user to assign as needed:

< p>

--> somelib.lua <--
local SomeLib = {... }
return SomeLib

--> usercode.lua <--
local theLib = require'somelib' - consumers name lib as they wish == good

The above pattern works fine when using a single file. However, when you have multiple mutual This can become quite difficult when citing files.

Specific examples

How to rewrite the following file suite to make all the assertions pass? Ideally, rewriting will keep the same files on disk and retain responsibility for each file. (Rewriting by merging all the code into a single file is effective, but it does not help;)

< p>

--> test_usage.lua <--
require'master'

assert(MASTER.Simple)
assert(MASTER.simple)
assert(MASTER.Shared)
assert(MASTER.Shared.go1)
assert(MASTER.Shared.go2)
assert(MASTER.Simple.ref1()==MASTER .Multi1)
assert(pcall(MASTER.Simple.ref2))
assert(_G.MASTER == nil) - Does not currently pass
--> master.lua <--
MASTER = {}
require'simple'
require'multi'
require'shared1'
require'shared2'
require'shared3 '
require'reference'

--> simple.lua <--
MASTER.Simple = {}
function MASTER:simple() end

--> multi.lua <--
MASTER.Multi1 = {}
MASTER.Multi2 = {}

--> shared1.lua <- -
MASTER.Shared = {}

--> shared2.lua <--
function MASTER.Shared:go1() end

- -> shared3.lua <--
function MASTER.Shared:go2() end

--> ref erence.lua <--
function MASTER.Simple:ref1() return MASTER.Multi1 end
function MASTER.Simple:ref2() MASTER:simple() end

Failed: Setting the environment

I want to solve the problem by setting the environment as the main table by self-referencing. But when calling functions like require, this does not work because they will change the environment:

--> master.lua <--
foo = "original"
local MASTER = setmetatable({foo="captured"},{__index=_G})< br />MASTER.MASTER = MASTER
setfenv(1,MASTER)
require'simple'

--> simple.lua <--
print(foo ) --> "original"
MASTER.Simple = {} --> attempt to index global'MASTER' (a nil value)

You give master.lua two responsibilities:

>It defines the general module table
>It imports all submodules

Instead, you should be (1) Create a separate module and import it into all submodules:

--> common.lua <--
return {}

--> master.lua <--
require'simple'
require'multi'
require'shared1'
require'shared2'
require'shared3'
require'reference'
return require'common' - return the common table

--> simple.lua <--
local MASTER = require'com mon' - import the common table
MASTER.Simple = ()
function MASTER:simple() end

wait

Finally, change test_usage. The first line of lua to use local variables:

--> test_usage.lua <--
local MASTER = require'master'
.. .

The test should now pass.

Leave a Comment

Your email address will not be published.