Recommended way to have 2+ modules recursively refer to each other in Lua 5.2
Asked Answered
M

3

7

Is there a way to have

  • Two Lua modules (let's call them A and B)
  • Each module uses functions from the other, so they must require each other
  • A third module (let's call it C) can use A but not B e.g.

C.lua:

local A = require 'A'

-- ...

A.foo()
  • There may be another module D that requires B but not A and/or E requiring both A and B
  • Neither A nor B nor their members should be added to the global namespace.
  • Avoid using the module and setfenv functions (deprecated in Lua 5.2)

Related: Lua - how do I use one lib from another? (note: this solution does not handle circular dependencies.)

Municipalize answered 23/11, 2011 at 20:35 Comment(0)
M
6

I found quite a simple way to do it:

A.lua:

local A = {}
local B

function A.foo()
    B = B or require 'B'
    return B.bar()
end

function A.baz()
    return 42
end

return A

B.lua:

local B = {}
local A

function B.bar()
    A = A or require 'A'
    return A.baz()
end

return B
Municipalize answered 23/11, 2011 at 20:48 Comment(3)
Is there any harm in calling require from within a function like this? I've only ever seen it used at the top level.Municipalize
There's no warning in the reference manual about it, so I think it's safe.Alloy
You can use require anywhere in the code. For example, I often use this idiom to start the RemDebug at a given point in file: if somecondition then require 'remdebug.engine'.start() endPerreira
S
3

A standard way to do this in any language is to introduce a mediator. Modules can then publish and subscribe to the mediator. http://en.wikipedia.org/wiki/Mediator_pattern

An example of this in my languages is mvccontrib bus, IEventAggregator, and MVVM Lite Messenger class. They all do the same thing.

Soriano answered 7/12, 2011 at 11:11 Comment(0)
M
3

Another method, suggested by Owen Shepherd on the lua-l mailing list:

If we set package.loaded[current-module-name] at the top of each module, then any other module required later can refer to the current (possibly incomplete) module.

A.lua:

local A = {}
package.loaded[...] = A

local B = require 'B'

function A.foo()
    return B.bar()
end

function A.baz()
    return 42
end

return A

B.lua:

local B = {}
package.loaded[...] = B

local A = require 'A'

function B.bar()
    return A.baz()
end

return B

This will not work everywhere. For example if B's initialization depends on A.baz then it will fail if A is loaded first, because B will see an incomplete version of A in which baz is not yet defined.

Municipalize answered 20/12, 2012 at 22:55 Comment(1)
if you don't want to call require the whole time (in each function as described by finnw' other solution), it seems this is the only way left to go. Imho it's quite nice too since the return modename at the end of your module is written to package.loaded[...] anyway (using this structure of not polluting global namespace and returning tables from modules)Braynard

© 2022 - 2024 — McMap. All rights reserved.