lua-users home
lua-l archive

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


On Mon, Oct 17, 2011 at 11:37 PM, Mark Hamburg <mark@grubmah.com> wrote:
> Let's run through what module does.
[snip]
> Obviously others differ, but I think it would be useful to hear which of these behaviors is deemed so valuable that people are fighting for it.

I read the anti-module wiki page, was convinced, drank the koolaid,
and started not using it

4 or 5 modules in I realized I was reimplementing the module()
function myself, which was lame, and I regret it. We've been switching
over to module() and enjoying it a bunch.

I'd say the single biggest problem with losing module is that we have
to port everything to 5.2, and do it in a way that also works for 5.1
(which will be here a long time).

Its a PITA, for pretty marginal benefit. So, its not primarily about
whether a no-module() world is marginally better, its that just when
we're starting to collect some lua modules, they all stop working!

And a number of the people coming out against module use it
extensively in their own code, its hard to understand them hating it
that much.

If 5.2 at the very least had a module(), where you could do

  local _ENV = module(..., package.seeall)

and a second argument to require so you could to

  require("some.mod", package.register)
  assert(some.mod)
or
  local mod = require"some.mod"

the situation would be better. For the common idiom of constructing
modules in 5.2 to immediately require advanced metatable knowledge,
seems unfortunate to me.

  local _ENV = setmetatable({}, {__index=_G})
  function foo() end
  return _ENV

> 1. It handles constructing module tables including the fields _NAME, _M, and _PACKAGE. This is useful if one cares about these fields, but I note that they don't appear in the pre-defined modules like table and string.

I use this in constructing modules from multiple files:

  zbee/
     init.lua -- this will require(_NAME..".app"), etc.
     app.lua
     link.lua

The module(...) doesn't self doc, but it follows DRY, don't repeat yourself.

It allows me to svn mv zbee to protocol/zbee, not change any of its
code, and the module has a different name (protocol.zbee). Useful also
because the test code in the zbee/ subdir can require the files
directly, and different applications using the module can refer to it
by different names, depending on how their package.path is set up.

> 2. It handles constructing submodules. I might question whether it makes sense to have socket.http available when socket is potentially just a stub table, but that's a more complicated matter.

I find hierarchical namespacing useful. I like that we can have
fw/pump/tcp.lua, and that  tcp is not the same as
protocol.tcp.lua. And doing

  local fwpumptcp = require"fw.pump.tcp"
  local protocoltcp = require"protocol.tcp"

strikes me as awkward and silly.

> 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.

This can bite you, but it can be a boon. In particular, notice that we
don't have to do a require"io" or require"table" before using the io
or table modules.... (with the standard lua interpreter). OMG! Hidden
dependencies! In a non-standard interp, like a lua_State that hasn't
had register all called on it, your code won't work... but I've
never seen any lua code call require"table"....

Everybody who _REALLY_ thinks having module names be global, I'd say
you should be lobbying for all the standard modules to be removed from
the global namespace. I'd be vehemently opposed, of course, but at
least you'd be consistent!

One thing I do agree with, is that whether the modules are injected
globally in nested tables should be a parameter to require, not a
side-effect of module.

But on the other hand, whats the global table for, if not for putting
modules? Surely you don't put your __variables__ in there, do you?

function that()
  -- BAD, BAD, BAD!
  i = 4
  j = 9
  return i + j
end

Our code doesn't use the global debug, but we do require our own
frameworks, and we want them globally available, so testcase plugins
can

  require"tcp.plugin"

and everything they might want is there. I don't want to start our
plugins with 18 lines of

  local blah = require"some.blah"

statements.

> 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.

I've never seen this be a problem in practice (not saying that it
can't be, of course). In particular, even python (which some people
seem to wish lua was more like :-(), and which makes it
extraordinarily hard to import anything so its global across all
modules, allows this so-called "pollution":

    -- bar.py
    import logging # logging is now available as bar.logging

The setting of the modules env to the module table doesn't just make
it "easy" to export functions, it makes it easy to not care whether
the functions is exported or not within the module;

module(....)

local function do_that() end

... lots of code that uses do_that()
----

When I decide I want do_that() exported, because its super useful, I
just remove the local, and its all good.

Contrast with the simplest of the many 5.2 module definition techniques:

local _M = {}

local function do_that() end
 ---[[ when this becomes
  function _M.do_that() end
all the code below will break, so then I guess we define a local
alias, for backwards compat?
 local do_that = _M.do_that -- LAME!
or we do a search and replace on the whole file, just because we now
export the function as part of the module?
]]

... lost of code that uses do that
return _M
------


Cheers,
Sam