lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I'm surprised I haven't thought of this sooner (nor seen this posted
elsewhere).  Here are two "module" function options that in a simple
way avoid the problem of a module's private namespace being polluted
to its public namespace.

  function package.clean(module)
    local privenv = {_PACKAGE_CLEAN = true}
    setfenv(3, setmetatable(privenv, {__index=_G,
__newindex=function(_,k,v) rawset(privenv,k,v); module[k]=v end}))
  end
  function package.veryclean(module)
    local privenv = {M=module, _PACKAGE_VERYCLEAN = true}
    setfenv(3, setmetatable(privenv, {__index=_G}))
  end

Examples:

  -- baz.lua
  module(..., package.clean)
  function foo() print 'test' end
  function bar() foo() end

  -- baz.lua  (alternative)
  module(..., package.veryclean)
  function M.foo() print 'test' end
  function M.bar() M.foo() end

  -- test.lua
  require "baz"
  assert(not baz.print) -- globals not exposed (unlike package.seeall)
  baz.bar() -- ok

This can also be combined with a reimplementation of strict.lua.  See
[1] for further details.

Still, my other complaints about the module function [2] still hold.
Mainly, the module function writes the module's external API to the
shared global environment (via the "luaL_findtable(L,
LUA_GLOBALSINDEX, modname, 1)" call in loadlib.c:ll_module) rather
than to the individual private namespaces of its clients.  Secondly,
the rules for merging the external APIs of two modules with a common
prefix (e.g. foo and foo.bar), as also defined in luaL_findtable,
break the isolation between foo and bar.  That is, bar is written
inside the foo table, and any other module subsequently doing only
require "foo" automatically gets foo.bar.  These two problems are
easily addressed by importing external APIs of modules only into local
variables of the clients (e.g. local bar = require "foo.bar"), but
there are also ways to address these by importing them into the
private environment of the clients via an import mechanism like
Perl/Python:

  import "foo"  -- instead of require "foo"

where "import" is roughly defined as

  function import(name)
    local env = getfenv(2)
    env[name] = require(name)
      -- warning: the above line is similar to luaL_findtable, but
this simple implementation
      -- does not handle dots "." in name.
  end

Unfortunately, we are still left with some possible conflicts when the
module function writes to the shared global environment.  The only way
I see around that is to redefine or avoid the module function.  I
think a suitable resolution along the former lines is to remove the
luaL_findtable call in ll_module and optionally move it into an import
statement like the above.  I'd argue that luaL_findtable is not a
module definition concern but an importation concern.  This also has
the added benefit that clients could define more than one type of
import statement for different types of importation behavior given a
single definition of module.

[1] http://lua-users.org/wiki/ModuleDefinition
[2] http://lua-users.org/wiki/LuaModuleFunctionCritiqued