A guide to building Lua modules

classic Classic list List threaded Threaded
92 messages Options
12345
Reply | Threaded
Open this post in threaded view
|

A guide to building Lua modules

Enrique Garcia Cota
Hi there,

I've written a series of articles about building Lua modules - it's here:


I would appreciate any feedback or comments on it.

Thanks and best regards,

Enrique ( kikito / @otikik )
Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Sean Conner
It was thus said that the Great Enrique Garcia Cota once stated:
> Hi there,
>
> I've written a series of articles about building Lua modules - it's here:
>
> http://kiki.to/blog/2014/03/30/a-guide-to-building-lua-modules/
>
> I would appreciate any feedback or comments on it.
>
> Thanks and best regards,

  Of all the articles listed, the one I had the most problem with was
article #3: Allow monkeypatching, which covers two different topics,
monkeypatching and locals.

  This is bad advice.  The purpose of locals is not necessarily a speed
optimization (although it is to a very small degree) but a way of consistent
behavior and not unduely modifying the global namespace (the major reason
for Lua's 5.2 change of how modules are loaded) *nor* the module namespace.

  In Lua 5.1, once you call module(), you loose the existing global
namespace, and because of that, you need to cache any existing modules you
use into local variables.  For Lua 5.2 you can do this (which I recommend),
but it is not mandatory [1][2].

  As for monkeypatching, yes, you can do it.  But it's not that common in
the Lua community (thank God!) and for good reason: you cannot reason about
monkeypatched code!  Sure, you might have a Good Reason to patch some
function, but like the long discussion about __index [3] showed, it can lead
to surprising results if you change the semantics of functions underneath
the functions that call the monkeypatched functions, and by "surprising
results" I mean "impossible to find bugs at 3:00 am on a major holiday" [2].

  And the example you give for monkeypatching, overriding the function
sum(), is a very bad one, because you are breaking the basic assumptions of
arthmetic.  Again, you are free to do that, but if I'm using a module you
wrote and I found that it changed the underlying semantics of a function I'm
using, I'll delete the module (and probably any other modules you wrote, as
I can no longer trust them).

  Again, it can't be said enough:  it's hard enough to debug code.  There's
no reason to make it harder.

  -spc (Did a proof-of-concept of monkeypatching a live executable---and
        felt horribly icky afterwards ... )

[1] In http://lua-users.org/wiki/ModulesTutorial there is code to inject
        the global environment as a backup for the module environment.  This
        I do not like as well.  One, because you can get to the global
        environment by mistake [2], and two, because of monkeypatching.

[2] It's hard enough to debug code.  There's no reason to make it
        harder.

[3] Start here: http://lua-users.org/lists/lua-l/2014-04/msg00517.html

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Dirk Laurie-2
In reply to this post by Enrique Garcia Cota
2014-04-14 11:03 GMT+02:00 Enrique Garcia Cota <[hidden email]>:

> I've written a series of articles about building Lua modules - it's here:
>
> http://kiki.to/blog/2014/03/30/a-guide-to-building-lua-modules/
>
> I would appreciate any feedback or comments on it.

Rule #1: Do what Lua does
   Agree 95%. The exception is table.insert, which violates the
   rule "One thing, and one thing only". As a consequence,
   table.insert(tbl,j,nil) and table.insert(tbl,j) do not mean the same.

   Your article could add: supply concise but precise documentation.

Rule #2: Always return a local table
   Agree 95%. The exception is when all that the module does is
   some monkeypatching :-)

Rule #3: Allow monkeypatching
   Agree 5%. No monkeypatching should be the default, and
   it should be documented clearly when a module allows it.
   Efficiency is not the only reason for caching system functions
   locally, and designing module functions to be definable abstractly
   is not all that easy.

Rule #4: Make stateless modules
   Agree 80%. The exceptions involve modules that are designed
   to be used preloaded in interactive sessions.

Rule #5: Beware of multiple files
   Agree 95%. The exception is when the module requires
   a module from the cpath.

Rule #6: Break the rules
    Agree 100%, when qualified by "but say why".

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Jeremy Ong
To offer a slightly dissenting opinion on one aspect of rule #3,
namely that while monkeypatching is a special tool for unique
circumstances, the developer need not go out of his way to prevent it
- I think I generally agree with that statement. When I write a module
for others, it's up to them if they want to abuse it or mess with it.
That's not really my concern since they implicitly know they are doing
something dangerous.

Generally, I think it's hard to predict when it will be useful to
monkeypatch, however rare it may be (a user-defined callback on every
invocation of a function for example). Better is to not try in my
opinion. Going out of your way to prohibit it on all exposed tables
certainly carries a certain code smell to me (fighting the language
semantics). Better perhaps is to isolate the code that should not be
tampered with and ensure that it is private.

On Mon, Apr 14, 2014 at 3:06 AM, Dirk Laurie <[hidden email]> wrote:

> 2014-04-14 11:03 GMT+02:00 Enrique Garcia Cota <[hidden email]>:
>
>> I've written a series of articles about building Lua modules - it's here:
>>
>> http://kiki.to/blog/2014/03/30/a-guide-to-building-lua-modules/
>>
>> I would appreciate any feedback or comments on it.
>
> Rule #1: Do what Lua does
>    Agree 95%. The exception is table.insert, which violates the
>    rule "One thing, and one thing only". As a consequence,
>    table.insert(tbl,j,nil) and table.insert(tbl,j) do not mean the same.
>
>    Your article could add: supply concise but precise documentation.
>
> Rule #2: Always return a local table
>    Agree 95%. The exception is when all that the module does is
>    some monkeypatching :-)
>
> Rule #3: Allow monkeypatching
>    Agree 5%. No monkeypatching should be the default, and
>    it should be documented clearly when a module allows it.
>    Efficiency is not the only reason for caching system functions
>    locally, and designing module functions to be definable abstractly
>    is not all that easy.
>
> Rule #4: Make stateless modules
>    Agree 80%. The exceptions involve modules that are designed
>    to be used preloaded in interactive sessions.
>
> Rule #5: Beware of multiple files
>    Agree 95%. The exception is when the module requires
>    a module from the cpath.
>
> Rule #6: Break the rules
>     Agree 100%, when qualified by "but say why".
>

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Philipp Janda
In reply to this post by Enrique Garcia Cota
Am 14.04.2014 11:03 schröbte Enrique Garcia Cota:
> Hi there,

Hi!

>
> I've written a series of articles about building Lua modules - it's here:
>
> http://kiki.to/blog/2014/03/30/a-guide-to-building-lua-modules/
>
> I would appreciate any feedback or comments on it.

Rule #1 (Do what Lua does):
One thing Lua does is "provide mechanism not policy"[1]. Yet you are
recommending that other people follow your rules. As of your rule #5 you
must have good reasons for this, and I'd be very interested in
discussing those with you.

   [1]: http://lua-users.org/wiki/MechanismNotPolicy

Rule #3 (Allow monkeypatching):
Lua being a dynamic language doesn't mean that you should monkey-patch
(or allow monkey-patching). Lua also is a language that allows you to
prevent monkey-patching for certain modules, so it basically comes down
to "Allow monkey-patching because I like monkey-patching" which by your
own definition isn't a good reason.
Regarding the "Beware of locals" part: I think it all depends on the
public interface of your module (whether implicit or documented
explicitly). In your example I would assume from the function names that
`add` adds, and `mult` multiplies. Nothing suggests that monkey-patching
`add` should change the behavior of `mult`. This is also exactly "what
Lua does", e.g. `table.insert` uses `rawget` and `rawset` internally
(well, `lua_rawget` and `lua_rawset`, but the principle is the same). Do
you expect `table.insert` to change its behavior if you monkey-patch
`rawget`? `print` on the other hand is documented to call `tostring` on
its arguments, so you can expect `print` to change its behavior if you
redefine `tostring`.
In most cases I consider the use of standard library functions in my
modules to be an implementation detail, so by caching them in locals I
_enable_ you to monkey-patch to your heart's desire without fear of
breaking the functions of my modules. And by monkey-patching you usually
want to make writing code more convenient. My modules can't profit from
that because they are already written ... and tested in a certain
environment ... and unaware of your enhancements.

And btw.: locals are faster than upvalues, so for tight loops it might
make sense to cache the upvalues defined at the beginning of the module
in locals ... :-p

Rule #5 (Beware of multiple files):
If you move your modules from `./my-package` to `./lib/my-package`, you
should change `package.path` and not your module references!
And your `current_folder` trick is (slightly) broken. When you run
`my-package/init.lua` via `require( "my-package" )` `...` will contain
"my-package", not "my-package.init", so you must not remove the last
module component in this case. If you call `require( "my-package.init"
)` however, the extra ".init" is there and must be removed to figure out
the package name. You can fix that by explicitly testing for ".init" at
the end of the module name.

If you don't use `my-package/init.lua` at all but `my-package.lua`
instead, things get much easier: You just use `local mod1 = require(
(...)..".module1" )` to get to your submodules. (Again this only matters
when you incorporate your module into a larger module hierarchy -- other
location changes can be handled with an update to `package.path`.)

>
> Thanks and best regards,
>
> Enrique ( kikito / @otikik )
>

Philipp




Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
In reply to this post by Enrique Garcia Cota
On Mon, Apr 14, 2014 at 2:03 AM, Enrique Garcia Cota <[hidden email]> wrote:

> Hi there,
>
> I've written a series of articles about building Lua modules - it's here:
>
> http://kiki.to/blog/2014/03/30/a-guide-to-building-lua-modules/
>
> I would appreciate any feedback or comments on it.
>
> Thanks and best regards,
>
> Enrique ( kikito / @otikik )

Looks pretty thorough, good job!  The only thing I saw that might be
worth mentioning is some modules monkey-patch globals, and should only
be run once -- but they export/expose nothing so they return boolean
'true'.  You can return anything in a module of course -- the value
becomes referenced from package.loaded.module_name = <return value>.
If a module returns false or nil, I believe it will be loaded from the
file on every require().

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
In reply to this post by Sean Conner
On Mon, Apr 14, 2014 at 2:55 AM, Sean Conner <[hidden email]> wrote:
>   As for monkeypatching, yes, you can do it.  But it's not that common in
> the Lua community (thank God!) and for good reason: you cannot reason about
> monkeypatched code!

Lol don't listen to Sean, he's crazy :p  Monkeypatching rules!
Everybody's doing it... wanna hit?

No, but in seriousness monkeypatching can be both a gift and a curse.
Usually I only do this when I'm writing code for myself because I know
in what way I might have modified table.insert() to accept a vararg.
It's easier to understand if you're adding functions rather than
redefining them, but he's right that it makes it more difficult for
third-parties to figure out how Lua has changed for you.  I wouldn't
say "don't do it", but I would say "don't go crazy" :-)  It has it
place in moderation.

Practically everyone in Lua 5.1/5.2 wrapped table.concat() to call
tostring() on the table elements anyway.  Some monkeys think alike :-)

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
In reply to this post by Dirk Laurie-2
On Mon, Apr 14, 2014 at 3:06 AM, Dirk Laurie <[hidden email]> wrote:

> Rule #6: Break the rules
>     Agree 100%, when qualified by "but say why".

You.  I like you.  Nooooobody remembers this rule :(

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Dirk Laurie-2
In reply to this post by Coroutines
2014-04-14 17:29 GMT+02:00 Coroutines <[hidden email]>:

> If a module returns false or nil, I believe it will be loaded from the
> file on every require().

Not quite. The go/nogo test is whether there is an entry for the module
in package.loaded.

$ lua
Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
> package.loaded.lpeg = "bazinga"
> lpeg = require"lpeg"
> =lpeg
bazinga

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
On Mon, Apr 14, 2014 at 8:55 AM, Dirk Laurie <[hidden email]> wrote:

> 2014-04-14 17:29 GMT+02:00 Coroutines <[hidden email]>:
>
>> If a module returns false or nil, I believe it will be loaded from the
>> file on every require().
>
> Not quite. The go/nogo test is whether there is an entry for the module
> in package.loaded.
>
> $ lua
> Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
>> package.loaded.lpeg = "bazinga"
>> lpeg = require"lpeg"
>> =lpeg
> bazinga
>

I thought it checked package.loaded.module_name for a boolean-true
value and returned that, otherwise it loaded the module (if it exists)
from the search paths, no?  If you return nil/false it'll be loaded
from the file every time?

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Paige DePol
In reply to this post by Coroutines
On Apr 14, 2014, at 10:37 AM, Coroutines <[hidden email]> wrote:

> On Mon, Apr 14, 2014 at 3:06 AM, Dirk Laurie <[hidden email]> wrote:
>
>> Rule #6: Break the rules
>>    Agree 100%, when qualified by "but say why".
>
> You.  I like you.  Nooooobody remembers this rule :(

Sir Pogsalot, I presume? ;)



Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Aapo Talvensaari
In reply to this post by Coroutines
On Mon, Apr 14, 2014 at 6:29 PM, Coroutines <[hidden email]> wrote:
Looks pretty thorough, good job!  The only thing I saw that might be
worth mentioning is some modules monkey-patch globals

I wish that there was a way to monkeypatch variables with monkeypatching scope limited.

E.g. a function that takes a string as an argument, and then patches more functions to that (right now the only way to add more string methods is to patch the global string metatable, or wrap string inside something else). Also for patching tables with limited scope (i.e. patching a table inside a function would affect the table only inside the function). Maybe we need a function like setmonkeypatches(variable, {}) (compare that to setmetatable).

I could use it for example here:
https://github.com/bungle/lua-resty-template/blob/master/README.md#template-helpers

(Adding methods to global string, and table types (not encouraged, though))

But I assume this only adds unneccessary cruft to the language, and probably also hard to debug situations (why does this variable have a function here, but not here?).
Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Dirk Laurie-2
In reply to this post by Coroutines
2014-04-14 18:03 GMT+02:00 Coroutines <[hidden email]>:

> On Mon, Apr 14, 2014 at 8:55 AM, Dirk Laurie <[hidden email]> wrote:
>> 2014-04-14 17:29 GMT+02:00 Coroutines <[hidden email]>:
>>
>>> If a module returns false or nil, I believe it will be loaded from the
>>> file on every require().
>>
>> Not quite. The go/nogo test is whether there is an entry for the module
>> in package.loaded.
>>
>> $ lua
>> Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
>>> package.loaded.lpeg = "bazinga"
>>> lpeg = require"lpeg"
>>> =lpeg
>> bazinga
>>
>
> I thought it checked package.loaded.module_name for a boolean-true
> value and returned that, otherwise it loaded the module (if it exists)
> from the search paths, no?  If you return nil/false it'll be loaded
> from the file every time?
>

The test is simply on the existence of the key in the table.
Whether it was put there by `require` is immaterial. If the
next `require` finds no key, it reloads, otherwise not.

> package.loaded.lpeg = "false"
> lpeg = require"lpeg"
> return lpeg
false
> package.loaded.lpeg = nil
> lpeg = require"lpeg"
> return lpeg
table: 0x91c6de0

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
In reply to this post by Aapo Talvensaari
On Mon, Apr 14, 2014 at 9:10 AM, Aapo Talvensaari
<[hidden email]> wrote:

> On Mon, Apr 14, 2014 at 6:29 PM, Coroutines <[hidden email]> wrote:
>>
>> Looks pretty thorough, good job!  The only thing I saw that might be
>> worth mentioning is some modules monkey-patch globals
>
>
> I wish that there was a way to monkeypatch variables with monkeypatching
> scope limited.
>
> E.g. a function that takes a string as an argument, and then patches more
> functions to that (right now the only way to add more string methods is to
> patch the global string metatable, or wrap string inside something else).
> Also for patching tables with limited scope (i.e. patching a table inside a
> function would affect the table only inside the function). Maybe we need a
> function like setmonkeypatches(variable, {}) (compare that to setmetatable).
>
> I could use it for example here:
> https://github.com/bungle/lua-resty-template/blob/master/README.md#template-helpers
>
> (Adding methods to global string, and table types (not encouraged, though))
>
> But I assume this only adds unneccessary cruft to the language, and probably
> also hard to debug situations (why does this variable have a function here,
> but not here?).

You could do much of this with proxy tables (empty tables where you
have a metamethod with a defined __index/__newindex to catch changes).
 It's a bit hard to grasp but here's some starter information:
http://www.lua.org/pil/13.4.4.html

It's not "easy" to disallow monkeypatching but that is how you would
go about getting that level of control.  In my experience with Lua,
it's a good thing to let other people shoot themselves in the foot --
it's when they submit bug reports about issues caused by their
monkeypatching that gets on my nerves, haha

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
In reply to this post by Dirk Laurie-2
On Mon, Apr 14, 2014 at 9:19 AM, Dirk Laurie <[hidden email]> wrote:

> 2014-04-14 18:03 GMT+02:00 Coroutines <[hidden email]>:
>> On Mon, Apr 14, 2014 at 8:55 AM, Dirk Laurie <[hidden email]> wrote:
>>> 2014-04-14 17:29 GMT+02:00 Coroutines <[hidden email]>:
>>>
>>>> If a module returns false or nil, I believe it will be loaded from the
>>>> file on every require().
>>>
>>> Not quite. The go/nogo test is whether there is an entry for the module
>>> in package.loaded.
>>>
>>> $ lua
>>> Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
>>>> package.loaded.lpeg = "bazinga"
>>>> lpeg = require"lpeg"
>>>> =lpeg
>>> bazinga
>>>
>>
>> I thought it checked package.loaded.module_name for a boolean-true
>> value and returned that, otherwise it loaded the module (if it exists)
>> from the search paths, no?  If you return nil/false it'll be loaded
>> from the file every time?
>>
>
> The test is simply on the existence of the key in the table.
> Whether it was put there by `require` is immaterial. If the
> next `require` finds no key, it reloads, otherwise not.
>
>> package.loaded.lpeg = "false"
>> lpeg = require"lpeg"
>> return lpeg
> false
>> package.loaded.lpeg = nil
>> lpeg = require"lpeg"
>> return lpeg
> table: 0x91c6de0
>

Okay, so it's simply a:

if package.loaded[modname] == nil then
    package.loaded[modname] = load_from_paths(modname)
end

return package.loaded[modname]

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
In reply to this post by Paige DePol
On Mon, Apr 14, 2014 at 9:10 AM, Paige DePol <[hidden email]> wrote:

> Sir Pogsalot, I presume? ;)

Hai :D

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Paige DePol
On Apr 14, 2014, at 11:24 AM, Coroutines <[hidden email]> wrote:

> On Mon, Apr 14, 2014 at 9:10 AM, Paige DePol <[hidden email]> wrote:
>
>> Sir Pogsalot, I presume? ;)
>
> Hai :D

Hi! LoL

Sorry everyone, I meant that original message to go off-list... my apologies for the noise!

~pmd~


Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Dirk Laurie-2
In reply to this post by Coroutines
2014-04-14 18:23 GMT+02:00 Coroutines <[hidden email]>:

> On Mon, Apr 14, 2014 at 9:19 AM, Dirk Laurie <[hidden email]> wrote:
>> 2014-04-14 18:03 GMT+02:00 Coroutines <[hidden email]>:
>>> On Mon, Apr 14, 2014 at 8:55 AM, Dirk Laurie <[hidden email]> wrote:
>>>> 2014-04-14 17:29 GMT+02:00 Coroutines <[hidden email]>:
>>>>
>>>>> If a module returns false or nil, I believe it will be loaded from the
>>>>> file on every require().
>>>>
>>>> Not quite. The go/nogo test is whether there is an entry for the module
>>>> in package.loaded.
>>>>
>>>> $ lua
>>>> Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
>>>>> package.loaded.lpeg = "bazinga"
>>>>> lpeg = require"lpeg"
>>>>> =lpeg
>>>> bazinga
>>>>
>>>
>>> I thought it checked package.loaded.module_name for a boolean-true
>>> value and returned that, otherwise it loaded the module (if it exists)
>>> from the search paths, no?  If you return nil/false it'll be loaded
>>> from the file every time?
>>>
>>
>> The test is simply on the existence of the key in the table.
>> Whether it was put there by `require` is immaterial. If the
>> next `require` finds no key, it reloads, otherwise not.
>>
>>> package.loaded.lpeg = "false"
>>> lpeg = require"lpeg"
>>> return lpeg
>> false
>>> package.loaded.lpeg = nil
>>> lpeg = require"lpeg"
>>> return lpeg
>> table: 0x91c6de0
>>
>
> Okay, so it's simply a:
>
> if package.loaded[modname] == nil then
>     package.loaded[modname] = load_from_paths(modname)
> end
>
> return package.loaded[modname]

Bazinga! My so-called demonstration had:

    package.loaded.lpeg = "false"

which beautifully prints the same as if I had coded

    package.loaded.lpeg = false

Actually, you were right.

Have I just maybe been watching too many old BBT episodes lately?

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Coroutines
On Mon, Apr 14, 2014 at 9:37 AM, Dirk Laurie <[hidden email]> wrote:

> Have I just maybe been watching too many old BBT episodes lately?

I think we're both guilty of that :-))

Reply | Threaded
Open this post in threaded view
|

Re: A guide to building Lua modules

Kevin Martin
In reply to this post by Enrique Garcia Cota

On 14 Apr 2014, at 10:03, Enrique Garcia Cota <[hidden email]> wrote:

I've written a series of articles about building Lua modules - it's here:
I would appreciate any feedback or comments on it.

I thought it was very interesting, and made a lot of good points. I have two comments:

o I think the discussion so far in relation to #3 seems to missed what I believe is the main point of monkey patching, that is being able to make big nasty changes quickly with minimum fuss. Chapter 7 of Lua Programming Gems shows an example of this kind of thing. I try my best not to use any code that makes specific efforts for me not to 'decorate' its behaviour. The author of a module is not in the position to decide whether it's a good idea or not for me to patch their code in my circumstances. That being said I am also strongly against any module, that changes the behavioural contract of a function, and I would certainly not permanently change the behaviour of table.concat as another poster suggested that 'Practically everyone' does.

As an example from personal experience, we found a bug in a module used to parse and specifically subtract datetimes. Fixing the bug properly would have required a new software release and an upgrade on 5 servers. However, adding something like (I forget the exact details) the following code to the start of the executing script solved the problem in 10 minutes. We were then free to create the new versions of the server software and redeploy on our own time scale. If the utils module had internally cached the original parseDateTime, we would have been in a different position!

do
local u = require("utils")
local opdt = u.parseDateTime
u.parseDateTime = function(t)
local rv = opdt(t)
rv.isdst = false
return rv
end
end

o A key point that seems to be missing from #5 is that not everybody loads modules from the filesystem. In our environment, we have two searchers, the package.preload searcher, and our own custom searcher that searches an SQLite3 database (our application deployment format). If your multi-file module won't work if I concatenate all the separate files together, then it's of no use to me. I think this can be achieved roughly as follows:

------- part of a module, file1.lua ---
do
local M = {}
M._REQNAME = "mymodule-part-1"

--define your
--module in M

if not package.preload[M._REQNAME] then
package.preload[M._REQNAME] = function() return M end
end
end

------ main part of module, file2.lua ------
do
local part1name = "mymodule-part-1"
local M = {}

if not (pcall(require, part1name)) then
--do your current folder trick and use loadfile/dofile
end
M._part1 = require(part1name)


return M
end

Thanks,
Kevin

12345