Conceptual problem with module

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

Conceptual problem with module

Chris Marrin

I really like require and module as they exist in 5.1 ... almost.

The trouble I have is the fact that the module function adds the module
name to the global table. That pollutes the namespace, which really
bothers me. What I want to do is this:

     local foo = require "foo"
     foo.bar()

Now, I can make my 'foo' module return a reference to itself and the
above will work. But if I used the module function when initializing
foo, there will also be a 'foo' in the global namespace.

Module used like this:

     module "foo"
     function bar() ... end

I can put the above into a file called foo.lua and the require function
will load it. But if I did only this, calling foo.bar() as above would
fail, because local foo would be nil, because I did not return anything
from the implementation. In order to get foo.lua to work I would have to
do this:

     local package = package
     module "foo"
     function bar() ... end
     return package.loaded.foo

Now, that's pretty lame. It would not be so bad if module actually
returned the reference to the module. Then I could at least go:

     local foo = module "foo"
     function bar() ... end
     return foo

That's better, but 'foo' is still added to the global namespace.

Why do modules get put into the global namespace? And why doesn't module
return a reference? Am I missing an easier way here?

--
chris marrin                ,""$,
[hidden email]          b`    $                             ,,.
                         mP     b'                            , 1$'
         ,.`           ,b`    ,`                              :$$'
      ,|`             mP    ,`                                       ,mm
    ,b"              b"   ,`            ,mm      m$$    ,m         ,`P$$
   m$`             ,b`  .` ,mm        ,'|$P   ,|"1$`  ,b$P       ,`  :$1
  b$`             ,$: :,`` |$$      ,`   $$` ,|` ,$$,,`"$$     .`    :$|
b$|            _m$`,:`    :$1   ,`     ,$Pm|`    `    :$$,..;"'     |$:
P$b,      _;b$$b$1"       |$$ ,`      ,$$"             ``'          $$
  ```"```'"    `"`         `""`        ""`                          ,P`
"As a general rule,don't solve puzzles that open portals to Hell"'
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Alex Queiroz
Hallo,

On 4/24/06, Chris Marrin <[hidden email]> wrote:

>
> Module used like this:
>
>      module "foo"
>      function bar() ... end
>
> I can put the above into a file called foo.lua and the require function
> will load it. But if I did only this, calling foo.bar() as above would
> fail, because local foo would be nil, because I did not return anything
> from the implementation. In order to get foo.lua to work I would have to
> do this:
>
>      local package = package
>      module "foo"
>      function bar() ... end
>      return package.loaded.foo
>

     No. All globals declared in the module will be put in the module
table, which will be returned by require().

--
-alex
http://www.ventonegro.org/
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Chris Marrin
Alex Queiroz wrote:

> Hallo,
>
> On 4/24/06, Chris Marrin <[hidden email]> wrote:
>
>>Module used like this:
>>
>>     module "foo"
>>     function bar() ... end
>>
>>I can put the above into a file called foo.lua and the require function
>>will load it. But if I did only this, calling foo.bar() as above would
>>fail, because local foo would be nil, because I did not return anything
>>from the implementation. In order to get foo.lua to work I would have to
>>do this:
>>
>>     local package = package
>>     module "foo"
>>     function bar() ... end
>>     return package.loaded.foo
>>
>
>
>      No. All globals declared in the module will be put in the module
> table, which will be returned by require().

But experience shows that, after you call 'module' there will be a
global variable (_G.foo in this case). If you look at ll_module() you
will see that it does indeed add this global variable.

--
chris marrin                ,""$,
[hidden email]          b`    $                             ,,.
                         mP     b'                            , 1$'
         ,.`           ,b`    ,`                              :$$'
      ,|`             mP    ,`                                       ,mm
    ,b"              b"   ,`            ,mm      m$$    ,m         ,`P$$
   m$`             ,b`  .` ,mm        ,'|$P   ,|"1$`  ,b$P       ,`  :$1
  b$`             ,$: :,`` |$$      ,`   $$` ,|` ,$$,,`"$$     .`    :$|
b$|            _m$`,:`    :$1   ,`     ,$Pm|`    `    :$$,..;"'     |$:
P$b,      _;b$$b$1"       |$$ ,`      ,$$"             ``'          $$
  ```"```'"    `"`         `""`        ""`                          ,P`
"As a general rule,don't solve puzzles that open portals to Hell"'
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Chris Marrin
In reply to this post by Alex Queiroz
Alex Queiroz wrote:

> Hallo,
>
> On 4/24/06, Chris Marrin <[hidden email]> wrote:
>
>>Module used like this:
>>
>>     module "foo"
>>     function bar() ... end
>>
>>I can put the above into a file called foo.lua and the require function
>>will load it. But if I did only this, calling foo.bar() as above would
>>fail, because local foo would be nil, because I did not return anything
>>from the implementation. In order to get foo.lua to work I would have to
>>do this:
>>
>>     local package = package
>>     module "foo"
>>     function bar() ... end
>>     return package.loaded.foo
>>
>
>
>      No. All globals declared in the module will be put in the module
> table, which will be returned by require().

Also, require returns whatever is returned by the loaded package. If
nothing is returned, it returns 'true' I believe.

--
chris marrin                ,""$,
[hidden email]          b`    $                             ,,.
                         mP     b'                            , 1$'
         ,.`           ,b`    ,`                              :$$'
      ,|`             mP    ,`                                       ,mm
    ,b"              b"   ,`            ,mm      m$$    ,m         ,`P$$
   m$`             ,b`  .` ,mm        ,'|$P   ,|"1$`  ,b$P       ,`  :$1
  b$`             ,$: :,`` |$$      ,`   $$` ,|` ,$$,,`"$$     .`    :$|
b$|            _m$`,:`    :$1   ,`     ,$Pm|`    `    :$$,..;"'     |$:
P$b,      _;b$$b$1"       |$$ ,`      ,$$"             ``'          $$
  ```"```'"    `"`         `""`        ""`                          ,P`
"As a general rule,don't solve puzzles that open portals to Hell"'
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Chris Marrin
Chris Marrin wrote:

> Alex Queiroz wrote:
>
>> Hallo,
>>
>> On 4/24/06, Chris Marrin <[hidden email]> wrote:
>>
>>> Module used like this:
>>>
>>>     module "foo"
>>>     function bar() ... end
>>>
>>> I can put the above into a file called foo.lua and the require function
>>> will load it. But if I did only this, calling foo.bar() as above would
>>> fail, because local foo would be nil, because I did not return anything
>>> from the implementation. In order to get foo.lua to work I would have to
>>> do this:
>>>
>>>     local package = package
>>>     module "foo"
>>>     function bar() ... end
>>>     return package.loaded.foo
>>>
>>
>>
>>      No. All globals declared in the module will be put in the module
>> table, which will be returned by require().
>
>
> Also, require returns whatever is returned by the loaded package. If
> nothing is returned, it returns 'true' I believe.
>

(sorry for replying to myself again...)

That's another good point though. If require DID always return the
loaded package (assuming module were called) then I would not have to
both returning it from the function at all.

--
chris marrin                ,""$,
[hidden email]          b`    $                             ,,.
                         mP     b'                            , 1$'
         ,.`           ,b`    ,`                              :$$'
      ,|`             mP    ,`                                       ,mm
    ,b"              b"   ,`            ,mm      m$$    ,m         ,`P$$
   m$`             ,b`  .` ,mm        ,'|$P   ,|"1$`  ,b$P       ,`  :$1
  b$`             ,$: :,`` |$$      ,`   $$` ,|` ,$$,,`"$$     .`    :$|
b$|            _m$`,:`    :$1   ,`     ,$Pm|`    `    :$$,..;"'     |$:
P$b,      _;b$$b$1"       |$$ ,`      ,$$"             ``'          $$
  ```"```'"    `"`         `""`        ""`                          ,P`
"As a general rule,don't solve puzzles that open portals to Hell"'
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Mildred Ki'Lya
In reply to this post by Chris Marrin

On Mon, 24 Apr 2006 15:44:08 -0700 Chris Marrin wrote:
>
> I really like require and module as they exist in 5.1 ... almost.
>
> The trouble I have is the fact that the module function adds the
> module name to the global table. That pollutes the namespace, which
> really bothers me. What I want to do is this:

Hi.

My solution:

--------------- the module file

-- ... contains the module name. We can change the filename, if we
-- use ... the module name will be updated.
package.loaded[...] = {} -- create the module table
module((...), package.seeall)

-- your code here

--------------- end of the module file

In fact the module function look for the table in package.loaded. If it
exists, it doesn't create it (and doesn't create the global table)

The manual doesn't says that module create a global table, but it does.
This is my little workaround.

In fact, I think it is better to do:
        package.loaded[...] =
                type(package.loaded[...])~="nil" and
                        package.loaded[...]
                or {}


Mildred
--
Mildred       <xmpp:[hidden email]> <http://mildred632.free.fr/>
Clef GPG :    <hkp://pgp.mit.edu> ou <http://mildred632.free.fr/gpg_key>
Fingerprint : 197C A7E6 645B 4299 6D37 684B 6F9D A8D6 [9A7D 2E2B]
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Diego Nehab-3
Hi,

There are two issues here. One is namespace polution. The
other is returning the namespace from require().

In most cases, require *will* return the namespace. Either
your package returns it directly, or it calls module()
(which sets the package.loaded table) and returns nil. In
either case, the namespace will be returned.

About the namespace problem, Mildred's solution does the
trick. If you don't want your module to polute the
namespace, set loaded[modname] to the namespace *before*
calling module().

Regards,
Diego.
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Luiz Henrique de Figueiredo
In reply to this post by Chris Marrin
> The trouble I have is the fact that the module function adds the module
> name to the global table.

How about redefining 'module'? Something like this (untested):

do
        local _module=module
        module=function(name,...)
                local v=_G[name]
                _module(name,...)
                _G[name]=v
        end
end

--lhf
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Greg Falcon
On 4/24/06, Luiz Henrique de Figueiredo <[hidden email]> wrote:

> > The trouble I have is the fact that the module function adds the module
> > name to the global table.
>
> How about redefining 'module'? Something like this (untested):
>
> do
>         local _module=module
>         module=function(name,...)
>                 local v=_G[name]
>                 _module(name,...)
>                 _G[name]=v
>         end
> end

I think in that case, _module would change the environment of your
temporary function, not the function that is calling your wrapper.

Greg F
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Mike Pall-4-2
In reply to this post by Chris Marrin
Hi,

Chris Marrin wrote:
> Why do modules get put into the global namespace? And why doesn't module
> return a reference? Am I missing an easier way here?

Well, then why bother using it? You are better off with:

  local _M = {}

  local function helper() ... end  -- not part of the module API
  function _M.foo() ... end
  function _M.bar() ... end

  return _M

The lost convenience of using global assignment for module
function definitions shouldn't worry you. In fact now you have
unrestricted access to all globals (you'd need package.seeall for
module()). It also provides a clear indication which functions
are part of the module API.

Bye,
     Mike
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Chris Marrin
In reply to this post by Diego Nehab-3
Diego Nehab wrote:
> Hi,
>
> There are two issues here. One is namespace polution. The
> other is returning the namespace from require().
>
> In most cases, require *will* return the namespace. Either
> your package returns it directly, or it calls module()
> (which sets the package.loaded table) and returns nil. In
> either case, the namespace will be returned.

Ok, I see my mistake. I understand all the little bits of logic in
ll_require and ll_module now! My problem was that I did a require
"WalkerAI" and a module "ai", so they were not the same!!! That problem
is solved. Thanks.

>
> About the namespace problem, Mildred's solution does the
> trick. If you don't want your module to polute the
> namespace, set loaded[modname] to the namespace *before*
> calling module().

This seems like a fair bit of hackery, especially the 'package.seeall'
function, needed to make the hand-crafted table work like a module.
Rewriting the module function would not be bad, except that it would
break any module that depended on the global behavior.

Adding a package.noglobal function would be nice, then you could go:

     module("WalkerAI", package.noglobal)

This function would remove the namespace from the global table. It would
require rewriting of the module function as well, to save the previous
value of the namespace. Something like this might work:


     local _module = module
     function module(name, ...)
         local _G = _G
         local package = package
         local v = _G[name]
         _module(name,...)
         if package.loaded[name].__NOGLOBAL ~= nil then
             _G[name] = v
         end
     end

     function package.noglobal(m)
         m.__NOGLOBAL = true
     end

How does that look?

--
chris marrin                ,""$,
[hidden email]          b`    $                             ,,.
                         mP     b'                            , 1$'
         ,.`           ,b`    ,`                              :$$'
      ,|`             mP    ,`                                       ,mm
    ,b"              b"   ,`            ,mm      m$$    ,m         ,`P$$
   m$`             ,b`  .` ,mm        ,'|$P   ,|"1$`  ,b$P       ,`  :$1
  b$`             ,$: :,`` |$$      ,`   $$` ,|` ,$$,,`"$$     .`    :$|
b$|            _m$`,:`    :$1   ,`     ,$Pm|`    `    :$$,..;"'     |$:
P$b,      _;b$$b$1"       |$$ ,`      ,$$"             ``'          $$
  ```"```'"    `"`         `""`        ""`                          ,P`
"As a general rule,don't solve puzzles that open portals to Hell"'
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Chris Marrin
In reply to this post by Mike Pall-4-2
Mike Pall wrote:

> Hi,
>
> Chris Marrin wrote:
>
>>Why do modules get put into the global namespace? And why doesn't module
>>return a reference? Am I missing an easier way here?
>
>
> Well, then why bother using it? You are better off with:
>
>   local _M = {}
>
>   local function helper() ... end  -- not part of the module API
>   function _M.foo() ... end
>   function _M.bar() ... end
>
>   return _M
>
> The lost convenience of using global assignment for module
> function definitions shouldn't worry you. In fact now you have
> unrestricted access to all globals (you'd need package.seeall for
> module()). It also provides a clear indication which functions
> are part of the module API.

But global assignment is just a convenience, it provides a sandbox for
the module, right? If you add a setfenv after the table creation, you
would get that, right? And if you did that, then you get the
"convenience" of global assignment. And you can still use 'local
function' for unexposed functions, couldn't you?

--
chris marrin                ,""$,
[hidden email]          b`    $                             ,,.
                         mP     b'                            , 1$'
         ,.`           ,b`    ,`                              :$$'
      ,|`             mP    ,`                                       ,mm
    ,b"              b"   ,`            ,mm      m$$    ,m         ,`P$$
   m$`             ,b`  .` ,mm        ,'|$P   ,|"1$`  ,b$P       ,`  :$1
  b$`             ,$: :,`` |$$      ,`   $$` ,|` ,$$,,`"$$     .`    :$|
b$|            _m$`,:`    :$1   ,`     ,$Pm|`    `    :$$,..;"'     |$:
P$b,      _;b$$b$1"       |$$ ,`      ,$$"             ``'          $$
  ```"```'"    `"`         `""`        ""`                          ,P`
"As a general rule,don't solve puzzles that open portals to Hell"'
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Chris Marrin
Chris Marrin wrote:
> ...
> But global assignment is just a convenience, it provides a sandbox for
                         isn't :-)

--
chris marrin                ,""$,
[hidden email]          b`    $                             ,,.
                         mP     b'                            , 1$'
         ,.`           ,b`    ,`                              :$$'
      ,|`             mP    ,`                                       ,mm
    ,b"              b"   ,`            ,mm      m$$    ,m         ,`P$$
   m$`             ,b`  .` ,mm        ,'|$P   ,|"1$`  ,b$P       ,`  :$1
  b$`             ,$: :,`` |$$      ,`   $$` ,|` ,$$,,`"$$     .`    :$|
b$|            _m$`,:`    :$1   ,`     ,$Pm|`    `    :$$,..;"'     |$:
P$b,      _;b$$b$1"       |$$ ,`      ,$$"             ``'          $$
  ```"```'"    `"`         `""`        ""`                          ,P`
"As a general rule,don't solve puzzles that open portals to Hell"'
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Mike Pall-4-2
In reply to this post by Chris Marrin
Hi,

Chris Marrin wrote:
> Mike Pall wrote:
> >The lost convenience of using global assignment for module
> >function definitions shouldn't worry you. In fact now you have
> >unrestricted access to all globals (you'd need package.seeall for
> >module()). It also provides a clear indication which functions
> >are part of the module API.
>
> But global assignment is just a convenience, it provides a sandbox for
> the module, right?

Well, but who needs this (pseudo) sandbox? Personally I try to
avoid the use of global variables (wherever they end up). Module
scoped local variables have better isolation properties and are
faster.

> If you add a setfenv after the table creation, you
> would get that, right? And if you did that, then you get the
> "convenience" of global assignment. And you can still use 'local
> function' for unexposed functions, couldn't you?

3 x Yes. But you loose access to the globals unless you cache a
reference to them before the setfenv or use an __index chain,
like package.seeall does.

Bye,
     Mike
Reply | Threaded
Open this post in threaded view
|

RE: Conceptual problem with module

Grosch Franz-Josef (CR/AEA3-Fr) *
In reply to this post by Chris Marrin
> Adding a package.noglobal function would be nice, then you could go:
>
>      module("WalkerAI", package.noglobal)
>
> This function would remove the namespace from the global
> table. It would
> require rewriting of the module function as well, to save the
> previous
> value of the namespace. Something like this might work:
>
>
>      local _module = module
>      function module(name, ...)
>          local _G = _G
>          local package = package
>          local v = _G[name]
>          _module(name,...)
>          if package.loaded[name].__NOGLOBAL ~= nil then
>              _G[name] = v
>          end
>      end
>
>      function package.noglobal(m)
>          m.__NOGLOBAL = true
>      end
>
> How does that look?

As someone said before, this will not work correctly. Try the following,
which works for my purposes:

local _module = module
function module(name, ...)
   -- visibility of _G and package does not change here
   local v = _G[name]
   local env = getfenv(1)   -- save original fenv of new module function
   _module(name, ...)       -- open a module with the renamed original
function
   setfenv(2, _M)           -- setfenv for declarations inside the new
module
   setfenv(1, env)          -- set back original fenv of new module
function
   -- '_G' and 'package' are again visible
   if package.loaded[name].__NOGLOBAL ~= nil then
        _G[name] = v
   end
end

Personally, I do not regard the global module tables as a conceptual
problem.

Userdefined modules and predefined modules ('string', 'table',
'coroutine', 'debug', 'math', 'os', 'io', 'package', and '_G' if you
like) are treated consistently. Even your code uses module 'package'
without requiring it before.

My point of view is: A module is a globally unique unit. As soon as it
is loaded (by require, preloaded or predefined) is has a globally unique
name.

I regard it as a design error, if I happen to build a system that loads
two different modules with the same name.

Best regards
Franz-Josef
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Mildred Ki'Lya

On Wed, 26 Apr 2006 10:45:23 +0200 Grosch Franz-Josef (CR/AEA3-Fr) *
wrote:
> My point of view is: A module is a globally unique unit. As soon as it
> is loaded (by require, preloaded or predefined) is has a globally
> unique name.
>
> I regard it as a design error, if I happen to build a system that
> loads two different modules with the same name.

With the require you don't have the choice ... modules names must be
different. And it is not a problem as long as you don't touch your
package.path.

But you may have a programm that uses a global variable and require a
module that itself require a module of the global variable's name.
If the module create a global table, the global variable used by the
programm will be deleted...

Mildred

--
Mildred       <xmpp:[hidden email]> <http://mildred632.free.fr/>
Clef GPG :    <hkp://pgp.mit.edu> ou <http://mildred632.free.fr/gpg_key>
Fingerprint : 197C A7E6 645B 4299 6D37 684B 6F9D A8D6 [9A7D 2E2B]
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Mark Hamburg-4
In reply to this post by Grosch Franz-Josef (CR/AEA3-Fr) *
on 4/26/06 1:45 AM, Grosch Franz-Josef (CR/AEA3-Fr) * at
[hidden email] wrote:

> My point of view is: A module is a globally unique unit. As soon as it
> is loaded (by require, preloaded or predefined) is has a globally unique
> name.

The problem with putting modules into the global namespace isn't collisions,
it's that it hides dependencies.

If you have to write:

    local foo = require "foo"

in order to access functions in the foo namespace, your program will be more
robust against changes than if you use the global variable foo which just
happens to be defined because you wrote:

    require "baz"

And the process of loading baz just happened to load foo.

Now, you may say "But I wouldn't rely on that behavior of baz" and of course
you wouldn't. Intentionally. The problem is that it becomes too easy to
start using foo and not even realize that you haven't done a require on it
because you don't get an error.

Do you check that you've included every header file you need when writing C
code or do you let the compiler check it for you? If the latter, then
indirect inclusions are compile errors waiting to happen.

Having require extend the global namespace is a runtime error waiting to
happen.

And as for consistency with the standard libraries, one should probably have
to use require to get them as well.

The counterpoint to this tirade against adding things to the global
namespace is that the needs in a scripting context and particularly an
interactive context are different from the needs in a program development
context.

Mark

Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Luiz Henrique de Figueiredo
> Having require extend the global namespace is a runtime error waiting to
> happen.

Perhaps you mean "module" here, not "require" -- "require" does not
impose any policy on what the thing being required does: it simply
returns whatever the init code for the module returns.

> And as for consistency with the standard libraries, one should probably have
> to use require to get them as well.

Probably. One way to ensure this would be to have linit.c set them as preloaded,
but not call the init functions.
--lhf
Reply | Threaded
Open this post in threaded view
|

Re: Conceptual problem with module

Mark Hamburg-4
on 4/27/06 3:09 AM, Luiz Henrique de Figueiredo at [hidden email]
wrote:

>> Having require extend the global namespace is a runtime error waiting to
>> happen.
>
> Perhaps you mean "module" here, not "require" -- "require" does not
> impose any policy on what the thing being required does: it simply
> returns whatever the init code for the module returns.

Yes. I meant "module". Oops.

Mark