modules, require, magic

classic Classic list List threaded Threaded
160 messages Options
12345678
Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Hisham Muhammad
On Wed, Oct 19, 2011 at 4:04 AM, Geoff Leyland
<[hidden email]> wrote:

> On 19/10/2011, at 6:25 PM, Hisham wrote:
>
>> In terms of "fixing module()", I think Fabio's implementation is right
>> on the money:
>>
>> http://article.gmane.org/gmane.comp.lang.lua.general/84283
>>
>> It shows that a sane implementation of module() can be done with
>> standard Lua mechanisms. Do any of the anti-module proponents see any
>> problems with that design?
>
> Would taking a shallow copy of the module to hide _G would make
>
> -- mod.lua
> module("mod")
> count = 1
> function increment() count = count + 1; print(count) end
>
>
> -- main.lua
> local mod = require("mod")
> print(mod.count)
> mod.increment()
> print(mod.count)
>
>
> print "1 2 1" rather than "1 2 2"?

Good point. Fabio mentioned that it would "freeze" global definitions
and your example shows how that can be a problem. Take 2, then: I
believe the following addresses this issue, and I couldn't spot any
side effects.

-------- begin loader.lua
-- clone _G so that globals from the program don't invade the module
local lua_libs = {}
for k,v in pairs(package.loaded._G) do
   lua_libs[k] = v
end

local function loader(name, modpath)
  local mod, env = {}, {}
  setmetatable(mod, { __index = function(t,k) return rawget(env, k) end })
  setmetatable(env, { __index = function(t,k) return rawget(mod, k) or
rawget(lua_libs, k) end, __newindex = mod })
  env.module = function (name)
    env._NAME = name
    return env
  end
  local fh = assert(io.open(modpath, 'rb'))
  local source = fh:read'*a'
  fh:close()
  local ret = assert(load(source, modpath, 'bt', env))(name)
  return ret or mod
end

-- replace Lua loader
package.searchers[2] = function (name)
  local modpath, msg = package.searchpath(name, package.path)
  if modpath then
    return loader, modpath
  else
    return nil, msg
  end
end
-------- end loader.lua

Again, feedback on these design proposals for module() are welcome.

-- Hisham
http://hisham.hm/ - http://luarocks.org/

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Geoff Leyland
On 19/10/2011, at 8:09 PM, Hisham wrote:

> Take 2, then: I believe the following addresses this issue, and I couldn't spot any side effects.
>
> -------- begin loader.lua
> -- clone _G so that globals from the program don't invade the module
> local lua_libs = {}
> for k,v in pairs(package.loaded._G) do
>   lua_libs[k] = v
> end
>
> local function loader(name, modpath)
>  local mod, env = {}, {}
>  setmetatable(mod, { __index = function(t,k) return rawget(env, k) end })
>  setmetatable(env, { __index = function(t,k) return rawget(mod, k) or
> rawget(lua_libs, k) end, __newindex = mod })
>  env.module = function (name)
>    env._NAME = name
>    return env
>  end
>  local fh = assert(io.open(modpath, 'rb'))
>  local source = fh:read'*a'
>  fh:close()
>  local ret = assert(load(source, modpath, 'bt', env))(name)
>  return ret or mod
> end
>
> -- replace Lua loader
> package.searchers[2] = function (name)
>  local modpath, msg = package.searchpath(name, package.path)
>  if modpath then
>    return loader, modpath
>  else
>    return nil, msg
>  end
> end
> -------- end loader.lua
>
> Again, feedback on these design proposals for module() are welcome.


That looks like it's on the right track.

What's the setmetatable(mod, { __index = function(t,k) return rawget(env, k) end }) for?  It doesn't look like env can ever get any members so why bother looking in it?

If that's the case, then you probably need to set the env.module function before you set env's metatable.  (but it's ok that env._NAME actually gets set in mod)

Does anyone have any insight into the overhead of a couple of closures per module and looking up modules fields through yet another __index?


Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

steve donovan
On Wed, Oct 19, 2011 at 9:41 AM, Geoff Leyland
<[hidden email]> wrote:
> That looks like it's on the right track.

I note that the definition of module() does not actually do anything
except set _NAME, so that you can create modules this way without
actually calling it.

-- fred.lua
--module 'fred'  -- ha, don't really need to call this!

function answer()
    return 42
end

function go ()
    show 'hello'
end

function show (s)
    print(s)
end

It gets away with the ugly package.seeall, but has much the same
behaviour - and sandboxers still need to be aware that globals can be
accessed through the module table.

local fred = require 'fred'
print(fred.answer())
fred.go()
print(fred.io) -- !!

Although the no-magic style still works, it automatically becomes
burdened by all the indirection, unless you're careful to give local
aliases up front.

steve d.

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Tony Finch
In reply to this post by Hisham Muhammad
Hisham <[hidden email]> wrote:
>
> For that reason, I think #5 produces the clearest programming idiom.
> Distinguishing private and public functions with "local function" and
> "function" is very logical, if you think of "module" abstractly, as a
> language feature.

One of the key goals of 5.2 is to put Lua's scoping on a more solid
conceptual foundation. Getting rid of setfenv except as a dirty debugging
feature is a key part of that.

Luiz's module pattern gives you 'local function foo" for private functions
and "function module.bar" for exported ones.

Tony.
--
f.anthony.n.finch  <[hidden email]>  http://dotat.at/
East Sole: Northerly or northwesterly 4 or 5, occasionally 6 at first. Rough
or very rough. Showers then fair. Good.

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

steve donovan
In reply to this post by Hisham Muhammad
On Wed, Oct 19, 2011 at 9:09 AM, Hisham <[hidden email]> wrote:
> and your example shows how that can be a problem. Take 2, then: I
> believe the following addresses this issue, and I couldn't spot any
> side effects.

Here's Take 3. This variant distinguishes between the module (which is
a fixed table) and the environment, which acts as a 'shadow table'
that can look up missing stuff in the known globals.

local function loader(name, modpath)
 local mod, env = {}, {}
 env.module = function (name)
   env._NAME = name
   return env
 end
 setmetatable(env, {
    __index = lua_libs, -- resolve known globals
    __newindex = function (t,key,value)
        mod[key] = value -- put functions and constants into module!
        rawset(t,key,value) -- and into the shadow table
    end
 })
 local fh = assert(io.open(modpath, 'rb'))
 local source = fh:read'*a'
 fh:close()
 local ret = assert(load(source, modpath, 'bt', env))(name)
 return ret or mod
end

The cost of more correctness here is a copy of the module table which
is used as the environment of the module; also anything exported in
this scheme is a constant!  Which may or may not be a good thing;
personally I don't like modules owning variables.

Non-magic style continues to work, with a little extra lookup
overhead. For these explicit modules, the environment should generally
be empty; note a side-effect is that misspellings in modules will not
affect the global state of the program.  (An option to lock the
environment after loading could be used for more strictness)

(It is entirely possible that a variant of this was proposed by David
M at some point, but I can't find the reference)

steve d.

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

steve donovan
On Wed, Oct 19, 2011 at 11:04 AM, steve donovan
<[hidden email]> wrote:
> On Wed, Oct 19, 2011 at 9:09 AM, Hisham <[hidden email]> wrote:
>> and your example shows how that can be a problem. Take 2, then: I
>> believe the following addresses this issue, and I couldn't spot any
>> side effects.

Take 4 should be easier to understand:

local function loader(name, modpath)
 local env = {}
 env.module = function (name)
   env._NAME = name
   return env
 end
 setmetatable(env, {
    __index = lua_libs, -- resolve known globals
 })
 local fh = assert(io.open(modpath, 'rb'))
 local source = fh:read'*a'
 fh:close()
 local ret = assert(load(source, modpath, 'bt', env))(name)
 if ret then return ret end -- explicit table was returned
 local mod = {}
 -- the module is a copy of the environment after initial loading
 for k,v in pairs(env) do
    mod[k] = v
 end
 return mod
end

Very little metamagic left, except the old trick used by package.seeall.

Here it's more explicit that what we call the module is actually a
copy of the environment used when loading modules.

Full code lives at https://gist.github.com/1297868

steve d.

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Hisham Muhammad
In reply to this post by Tony Finch
On Wed, Oct 19, 2011 at 6:28 AM, Tony Finch <[hidden email]> wrote:

> Hisham <[hidden email]> wrote:
>>
>> For that reason, I think #5 produces the clearest programming idiom.
>> Distinguishing private and public functions with "local function" and
>> "function" is very logical, if you think of "module" abstractly, as a
>> language feature.
>
> One of the key goals of 5.2 is to put Lua's scoping on a more solid
> conceptual foundation. Getting rid of setfenv except as a dirty debugging
> feature is a key part of that.
>
> Luiz's module pattern gives you 'local function foo" for private functions
> and "function module.bar" for exported ones.

But having to write "module.bar" (or "M.bar", or "_M.bar"...) in every
use of module functions within the module is cumbersome. Someone might
argue that it "documents" the code, but that's only in the same sense
that Hungarian notation documents the code. If I decide to make a
function public I'd have to scan for all references to it and add
"module." or "M", or "_M"...

The alternative of having an "exports table" in the bottom with
"return { foo = foo, bar = bar, ... }" is a throwback to the
duplicated-declarations world of .c and .h files, except that at least
in C you can easily spot that a function declaration is private or
public through "static"; with the "exports table" one would have to
check back and forth at the bottom of the source file.

None of them are as clean as the programming style with module().

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Roberto Ierusalimschy
In reply to this post by Hisham Muhammad
> In terms of "fixing module()", I think Fabio's implementation is right
> on the money:
>
> http://article.gmane.org/gmane.comp.lang.lua.general/84283

What exaclty is it trying to fix?


> It shows that a sane implementation of module() can be done with
> standard Lua mechanisms. Do any of the anti-module proponents see any
> problems with that design? Would you hate module() as much as you do
> now if it behaved like that instead of the way it behaves in Lua 5.1?

Isn't it even more complex/magic than the current implementation?

If you really want 'module', wouldn't be simpler to use

_ENV = module(...)

with a simplified version of 'module'?

-- Roberto

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Fabio Mascarenhas
On Wed, Oct 19, 2011 at 1:32 PM, Roberto Ierusalimschy
<[hidden email]> wrote:
>> In terms of "fixing module()", I think Fabio's implementation is right
>> on the money:
>>
>> http://article.gmane.org/gmane.comp.lang.lua.general/84283
>
> What exaclty is it trying to fix?

1. anything provided to the module through its environment becomes
available to the module's clients (this is the
2. "module" exporting the module to the global environment by default

The first is the biggest wart of "module", IMHO, and the infamous
http://lua-users.org/wiki/LuaModuleFunctionCritiqued seems to agree.
The problem of a module seeing other modules that it did not require
is related to "2" in this implementation, but it can be solved
independently of "2" easily enough.

>
>> It shows that a sane implementation of module() can be done with
>> standard Lua mechanisms. Do any of the anti-module proponents see any
>> problems with that design? Would you hate module() as much as you do
>> now if it behaved like that instead of the way it behaves in Lua 5.1?
>
> Isn't it even more complex/magic than the current implementation?

The implementation is more complex, but the end result is that a
module exports its top-level definitions and only those, while still
having a richer environment. There is still a wart in that modules
become immutable in practice (although the module can still make
changes to its external interface by going through package.loaded),
but I believe most Lua code assumes that a module does not change once
its loaded, given that caching module definitions is a common idiom.

> If you really want 'module', wouldn't be simpler to use
>
> _ENV = module(...)
>
> with a simplified version of 'module'?

I assume you would also need to "return _ENV" in the end of the module
for this to work, this is unecessary boilerplate if we have the nice
"loadin" mechanism in the standard library. It does not solve problem
"1" above, either.

On the *why* of having a tighter policy for modules, I believe Hisham
has already made a great case for that.

> -- Roberto
>
>

--
Fabio Mascarenhas

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Roberto Ierusalimschy
> > If you really want 'module', wouldn't be simpler to use
> >
> > _ENV = module(...)
> >
> > with a simplified version of 'module'?
>
> I assume you would also need to "return _ENV" in the end of the module
> for this to work, [...]

I do not think so. 'module' might use the same trick (technique?) it
currently uses, regarding the 'loaded' table.

-- Roberto

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Hisham Muhammad
In reply to this post by Roberto Ierusalimschy
On Wed, Oct 19, 2011 at 1:32 PM, Roberto Ierusalimschy
<[hidden email]> wrote:
>> In terms of "fixing module()", I think Fabio's implementation is right
>> on the money:
>>
>> http://article.gmane.org/gmane.comp.lang.lua.general/84283
>
> What exaclty is it trying to fix?

Mainly, concerns #4 and #5 raised by Mark Hamburg:

> 4. It adds the module to the global namespace. This, in my opinion, is a bad
> thing because it creates hidden dependencies -- i.e., code can use a module
> that it never required simply because some other code required it earlier and
> it became available in the global namespace.
>
> 5. It mucks with the environment to make it "easy" to export functions. But
> then to compensate for this, it offers package.seeall which results in a
> module that reveals a lot of globals in its table which have nothing to do
> with the module -- i.e., it pollutes the API for the module.

...while trying to keep the good intentions of #5 (making it
easy/clean to export functions).

>> It shows that a sane implementation of module() can be done with
>> standard Lua mechanisms. Do any of the anti-module proponents see any
>> problems with that design? Would you hate module() as much as you do
>> now if it behaved like that instead of the way it behaves in Lua 5.1?
>
> Isn't it even more complex/magic than the current implementation?

I think that was in the right direction. Geoff Leyland and Steve
Donovan provided valuable feedback, here's an improved version:

-------- begin loader5.lua ( https://gist.github.com/1298652 )
local lua_libs = {}
for k,v in pairs(package.loaded._G) do
   lua_libs[k] = v
end

local function loader(name, modpath)
  local mod, env = {}, {}
  env.module = function (name)
    mod._NAME = name
    setmetatable(env, { __index = function(t,k) return rawget(mod, k)
or rawget(lua_libs, k) end, __newindex = mod })
    return env
  end
  local fh = assert(io.open(modpath, 'rb'))
  local source = fh:read'*a'
  fh:close()
  local ret = assert(load(source, modpath, 'bt', env))(name)
  return ret or mod
end

-- replace Lua loader
package.searchers[2] = function (name)
  local modpath, msg = package.searchpath(name, package.path)
  if modpath then
    return loader, modpath
  else
    return nil, msg
  end
end
-------- end loader5.lua

In this version, module definitions are not frozen (as per Geoff
Leyland's concern) and no environment overhead is added to modules
that choose not to use module() (as per Steve Donovan's concern).

It may be more complex/magic than the Lua 5.1 implementation of
module() (is this revision as well?), but it does what we all wanted
module() to do (declare a module and allow declaration of public and
private entries through the environment), while giving users
convenience equivalent to that people use package.seeall (another
popular construct) for, without the widely discussed drawbacks of Lua
5.1 module().

> If you really want 'module', wouldn't be simpler to use
>
> _ENV = module(...)
>
> with a simplified version of 'module'?

I tried to come up with a simplified version of module() to see how
close I could get to the behavior of loader5 above. This is as far as
I could get:

-------- begin simplifiedmodule.lua
local lua_libs = {}
for k,v in pairs(package.loaded._G) do
   lua_libs[k] = v
end

function module(name)
  local mod, env = {}, {}
  mod._NAME = name
  setmetatable(env, { __index = function(t,k) return rawget(mod, k) or
rawget(lua_libs, k) end, __newindex = mod })
  return env, mod
end
-------- end simplifiedmodule.lua

-------- begin simple.lua
local mod
_ENV, mod = module("simple")

print(_ENV, mod)

local function bar()
   print("hello")
end

function foo()
   bar()
end

return mod
-------- end simple.lua

-------- begin usesimple.lua
local simple = require("simple")
simple.foo()
print(simple.print)
print(simple._NAME)
-------- end usesimple.lua

Run it with:

lua52 -e'dofile("simplifiedmodule.lua")' usesimple.lua

To my surprise, this causes a segmentation fault in Lua 5.2 beta
(confirmed by Fabio). Commenting the "local mod" in simple.lua:1 makes
the segfault go away but it still doesn't work as I expected (I
probably don't understand _ENV completely).

Still, to get that behavior the simplified module() ends up not being
much simpler than that from loader5, which dispenses with the explicit
_ENV and return.

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Tony Finch
In reply to this post by Hisham Muhammad
Hisham <[hidden email]> wrote:
>
> But having to write "module.bar" (or "M.bar", or "_M.bar"...) in every
> use of module functions within the module is cumbersome.

It is wrong to use _M because that abuses a reserved namespace.

> None of them are as clean as the programming style with module().

Muddling up the global and module namespaces is not clean.
(Argumentative point, sorry. We've had too much of that in this thread.)

It's easy to reverse the priority of the global and module namespaces,
if you don't like repeated "module." prefixes:

        local G, _ENV = _ENV, module

I think the naming problem is the main reason people like module(), but it
provides nice names by breaking abstractions which the main reason people
dislike it. But there is more to the naming problem than just module().

A few times in this discussion people have mentioned the import problem,
e.g. from strict.lua,

        local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget

where the commonly suggested improvement is

        local error, rawset, rawget in _ENV
        local getinfo in debug

There is a similar export problem, e.g. at the end of a module,

        return { foo = foo, bar = bar }

When importing we are projecting from a table into a set of local
variables and when exporting we are injecting a set of local variables
into a table. In both cases we currently have to repeat the names to
get quoted and unquoted versions. (Note that syntactic sugar hides the
quotes.)

It is hard to come up with a table injection syntax that is as neat as the
projection syntax. Here's one that I don't much like. Any better
suggestions?

        return { in foo, bar }

The require function also has this double use+mention problem:

        local socket = require "socket"

I wonder if local vars in tbl could be generalized to allow a function on
the right hand side, e.g. the following, though you would need a
multi-valued version of require with a better name :-)

        local socket in require

I also wonder if there's a deeper pattern that could lead to a better
syntax for all three of these use+mention examples. Are there other cases
where use+mention causes tiresome code in Lua?

Tony.
--
f.anthony.n.finch  <[hidden email]>  http://dotat.at/
Biscay: Mainly north 4 or 5. Moderate or rough. Showers. Moderate or good,
occasionally poor at first in south.

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Petite Abeille
In reply to this post by Roberto Ierusalimschy

On Oct 19, 2011, at 5:32 PM, Roberto Ierusalimschy wrote:

> If you really want 'module', wouldn't be simpler to use
>
> _ENV = module(...)
>
> with a simplified version of 'module'?

By simplified, do you mean a 5.2 version not doing any of the setfenv() mambo jambo of the 5.1 implementation, but otherwise equivalent in functionality?

With the environment switching as an explicit _ENV assignment?

If yes, this sounds reasonable enough :)
Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Hisham Muhammad
In reply to this post by Roberto Ierusalimschy
On Wed, Oct 19, 2011 at 2:44 PM, Roberto Ierusalimschy
<[hidden email]> wrote:

>> > If you really want 'module', wouldn't be simpler to use
>> >
>> > _ENV = module(...)
>> >
>> > with a simplified version of 'module'?
>>
>> I assume you would also need to "return _ENV" in the end of the module
>> for this to work, [...]
>
> I do not think so. 'module' might use the same trick (technique?) it
> currently uses, regarding the 'loaded' table.

Well noted. Doing this gets us the following:

-------- begin simplifiedmodule.lua
local lua_libs = {}
for k,v in pairs(package.loaded._G) do
  lua_libs[k] = v
end

function module(name)
 local mod, env = {}, {}
 mod._NAME = name
 setmetatable(env, { __index = function(t,k) return rawget(mod, k) or
rawget(lua_libs, k) end, __newindex = mod })
 package.loaded[name] = mod
 return env
end
-------- end simplifiedmodule.lua

-------- begin simple.lua
_ENV = module("simple")

local function bar()
  print("hello")
end

function foo()
  bar()
end
-------- end simple.lua

-------- begin usesimple.lua
local simple = require("simple")
simple.foo()
print(simple.print)
print(simple._NAME)
-------- end usesimple.lua

Run it with:

lua52 -e'dofile("simplifiedmodule.lua")' usesimple.lua

Certainly an improvement from handling the module table explicitly
(and no segfaults this time). But having basically the same function
being provided by the loader[1] would obviate the need for explicitly
mentioning _ENV.

[1] https://gist.github.com/1298652

-- Hisham
http://hisham.hm/ - http://luarocks.org/

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Roberto Ierusalimschy
In reply to this post by Petite Abeille
>
> On Oct 19, 2011, at 5:32 PM, Roberto Ierusalimschy wrote:
>
> > If you really want 'module', wouldn't be simpler to use
> >
> > _ENV = module(...)
> >
> > with a simplified version of 'module'?
>
> By simplified, do you mean a 5.2 version not doing any of the setfenv() mambo jambo of the 5.1 implementation, but otherwise equivalent in functionality?
>
> With the environment switching as an explicit _ENV assignment?

For instance (also without the global setting). Something like this:

function module (name, ...)
  local env = package.loaded[name]
  if env == nil then
    env = {}
    package.loaded[name] = env
  end
  env.NAME = name
  env.M = env
  for _, f in ipairs{...} do
    f(env)
  end
  return env
end

-- Roberto

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Petite Abeille
In reply to this post by Hisham Muhammad

On Oct 19, 2011, at 7:20 PM, Hisham wrote:

> Well noted. Doing this gets us the following:

Alternatively, perhaps this can be boiled down to something along these lines:

function module( aName, ... )
    local aModule = package.loaded[ aName ]
   
    if type( aModule ) ~= 'table' then
        aModule = {}
        aModule._M = aModule
        aModule._NAME = aName
        aModule._PACKAGE = aName:sub( 1, aName:len() - ( aName:reverse():find( '.', 1, true ) or 0 ) )
       
        package.loaded[ aName ] = aModule
       
        for anIndex = 1, select( '#', ... ) do
            select( anIndex, ... )( aModule )
        end
    end

    return aModule
end


http://dev.alt.textdrive.com/browser/HTTP/debugx.lua#L79



Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Petite Abeille
In reply to this post by Roberto Ierusalimschy

On Oct 19, 2011, at 7:26 PM, Roberto Ierusalimschy wrote:

> For instance (also without the global setting). Something like this:
>
> function module (name, ...)
>  local env = package.loaded[name]
>  if env == nil then
>    env = {}
>    package.loaded[name] = env
>  end
>  env.NAME = name
>  env.M = env
>  for _, f in ipairs{...} do
>    f(env)
>  end
>  return env
> end

Hehe... I guess we criss-crossed the exact same proposal :))

function module( aName, ... )
   local aModule = package.loaded[ aName ]

   if type( aModule ) ~= 'table' then
       aModule = {}
       aModule._M = aModule
       aModule._NAME = aName
       aModule._PACKAGE = aName:sub( 1, aName:len() - ( aName:reverse():find( '.', 1, true ) or 0 ) )

       package.loaded[ aName ] = aModule

       for anIndex = 1, select( '#', ... ) do
           select( anIndex, ... )( aModule )
       end
   end

   return aModule
end

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Philipp Janda
In reply to this post by steve donovan
On 19.10.2011 11:53, steve donovan wrote:

> On Wed, Oct 19, 2011 at 11:04 AM, steve donovan
> <[hidden email]>  wrote:
>> On Wed, Oct 19, 2011 at 9:09 AM, Hisham<[hidden email]>  wrote:
>>> and your example shows how that can be a problem. Take 2, then: I
>>> believe the following addresses this issue, and I couldn't spot any
>>> side effects.
>
> Take 4 should be easier to understand:
>
> local function loader(name, modpath)
>   local env = {}
>   env.module = function (name)
>     env._NAME = name
>     return env
>   end
>   setmetatable(env, {
>      __index = lua_libs, -- resolve known globals
>   })
>   local fh = assert(io.open(modpath, 'rb'))
>   local source = fh:read'*a'
>   fh:close()
>   local ret = assert(load(source, modpath, 'bt', env))(name)
>   if ret then return ret end -- explicit table was returned
>   local mod = {}
>   -- the module is a copy of the environment after initial loading
>   for k,v in pairs(env) do
>      mod[k] = v
>   end
>   return mod
> end
>
> Very little metamagic left, except the old trick used by package.seeall.
>
> Here it's more explicit that what we call the module is actually a
> copy of the environment used when loading modules.
>

Looks acceptable to me if you replace
env.module = function (name) ... end
with
env._NAME = name

The module already has a name, it is passed to require and the loader
function. There is no need for a second name (any more). Even when the
second name (the "module-name" as opposed to the "require-name") was
used to define the name of the exported globals it just
presented two opportunities for name clashes instead of one and possibly
serious name confusion if "require-name" and the names of the globals
differed.
Without an explicit second "module-name" there is no reason to keep
module either.
I like about this proposal, that you cannot (accidentally or
intentionally) define globals, but, as I wrote elsewhere, I'm not sure
you should in general ban this from all require'd Lua code.
I still wouldn't use the "module is environment copy" feature, because
- sometimes I return non-tables from modules (typically functions)
- almost all of my modules have a __call-metamethod (for constructors or
for returning a 'properly configured' module table) and some also an
__index-metamethod (for inheritance)
- exported functions can't be confused with predeclared local functions.

Philipp



Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

steve donovan
On Wed, Oct 19, 2011 at 7:34 PM, Philipp Janda <[hidden email]> wrote:
> I still wouldn't use the "module is environment copy" feature, because
> - sometimes I return non-tables from modules (typically functions)

In fact I have some modules that just return strings. But these should
be ok, because this copying only happens when there is no return from
a module - that is, when it's intended to be a 'classic module'

But it's true that we lose flexibility with modules being simple
copies of the environment.

steve d.

Reply | Threaded
Open this post in threaded view
|

Re: modules, require, magic

Philipp Janda
In reply to this post by Petite Abeille
On 19.10.2011 19:31, Petite Abeille wrote:

>
> [...]
>
> function module( aName, ... )
>     local aModule = package.loaded[ aName ]
>
>     if type( aModule ) ~= 'table' then
>         aModule = {}
>         aModule._M = aModule
>         aModule._NAME = aName
>         aModule._PACKAGE = aName:sub( 1, aName:len() - ( aName:reverse():find( '.', 1, true ) or 0 ) )
>
>         package.loaded[ aName ] = aModule
>
>         for anIndex = 1, select( '#', ... ) do
>             select( anIndex, ... )( aModule )
>         end
>     end
>
>     return aModule
> end
>

What happens if the name passed to require and the name passed to module
differ? Wouldn't require just return true and the actual module ends up
somewhere hidden in package.loaded? It seems to me that the name
parameter to module might be problematic if you don't set globals.

Maybe you shouldn't unconditionally replace all non-table values from
package.loaded in case some other module placed a function or a string
there (which could happen if "require-name" and "module-name" differed).
The proposed module-function doesn't mess with globals anymore, but it
does mess with package.loaded and therefore should make sure that it
does not disrupt other legitimate uses of package.loaded.

Philipp


12345678