Using modules for namespaces is somewhat unwieldy. I'd call it one
of the few evil parts of Ruby, although its implementation is
understandable when viewed from an OO standpoint. It's just
tiresome to A) invent and B) use unique namespaces in Ruby (and
by 'unique' I mean FooSoft Inc. wrapping all their libraries inside a
module FooSoft).
Of the current commercial languages, the package system of Java is
likely to be the best for flexibility and uniqueness, although it
does suffer from verbosity--and I wouldn't mind seeing Haskell-style
limited export capabilities, either. This, of course, should be
possible to do in Ruby 'on top' of the current system.
But why use unique namespaces? Modules are certainly useful for
partitioning a project into its constituent parts, but unique
namespaces are only useful in the context of that code being used
by another party where it can prevent namespace clashes. This, of
course, is highly site-dependent: it may not be necessary at all,
they may already have a package with the same 'unique' namespace,
they may not like the module name, etc. etc.
Why not have the 'client' decide if and which namespace to assign
to a foreign library? As in, if I want to use FooBar Inc.'s Math
package, they need not wrap it in module FooBar, but can just ship
it as Math. Then I just wrap it to Extern, my namespace for all
third-party modules. In current Ruby, I suppose this can be achieved
with something like (please correct if I'm wrong, this is off the top
of my head):
-----
# Toplevel
# This is just an aesthetically pleasing (?) implementation,
# I'm not sure about using String for the ascribed purpose.
def import file
file[:module].module_eval(IO.readlines(file[:name]).join)
end
class String
def as module
{:name => self, :module => module}
end
# Convenience
alias :in :as
alias :into :as
end
# Usage
import "foomath.rb".as Extern
rand = Extern::Math.random()
-----
Or is that just counterintuitive? It would at least conceptually
free the module construct from doing double duty as the namespace
construct.
The Ruby implementation is obviously somewhat slow but it could be
written to use the same routines as require()/load() with the
exception of doing the loading in the context of a given module. I
think the above 'implementation' also handles altogether non-moduled
files but I may be wrong in my it's-Sunday-morning-and-I'm-at-work
stupor.
E
I like the idea very much, but there are some difficulties...
def import file
file[:module].module_eval(IO.readlines(file[:name]).join)
end
class String
def as mod
{:name => self, :module => mod}
end
end
module Extern; end
# It's a little awkward, but you gotta do this first, if
# you want to refer to the module as below.
libfile = "/usr/local/lib/ruby/1.8/complex.rb"
# One problem is the cumbersome path name to the library file.
import libfile.as(Extern)
# This fails because the library complex.rb tries to open an existing
# class, Numeric, and modify its contents. But instead it opens a new
# module, also called Numeric, that lives inside the wrapper. You can
# fix this in complex.rb (assuming you want to touch 3rd party code)
# by referring to the module as ::Numeric when you define it. But
# there's still a problem...
z = Extern::Complex(1,2)
# "undefined method `Complex' for Extern::Complex"
# This is due to the special effect of "def foo" at the top level in
# ruby--it defines an instance method of Object. But the effect within
# the wrapper module is very different. Again it's possible to modify
# the 3rd party code, as follows:
#
# class ::Object
# def Complex(a, b = 0)
# # ...
# end
# end
I see you caught my use of 'module' there.
This is the easier problem to solve:
def import file
# Top-level
module_eval "module #{file[:module]}; end"
file[:module].module_eval(IO.readlines(file[:name]).join)
end
> libfile = "/usr/local/lib/ruby/1.8/complex.rb"
> # One problem is the cumbersome path name to the library file.
Could set it to look up simple filenames through
the standard ruby paths. Ideally, of course, one
wouldn't be importing the core libraries anyway,
since they're assumed to be habiting the global
namespace.
> import libfile.as(Extern)
> # This fails because the library complex.rb tries to open an existing
> # class, Numeric, and modify its contents. But instead it opens a new
> # module, also called Numeric, that lives inside the wrapper. You can
> # fix this in complex.rb (assuming you want to touch 3rd party code)
> # by referring to the module as ::Numeric when you define it. But
> # there's still a problem...
Here I am modifying String and I didn't think of anyone
doing the same :)
A solution would be to modify the loaded file String
before eval()ing it: do a
.. =~ /(?:module|class\s(\w+?)/
if $ALL_STANDARD_MODULE_AND_CLASS_NAMES.has_key? $1
# Escape the module name with ::
end
This (obviously) only works for standard modules and
classes and it doesn't take into account top-level
functions... (have to do def Kernel.foo explicitly if
necessary -any use of that function inside the imported
module would be sort of 'global' since it's in the same
scope anyway) but I'm not sure if it'd be necessary to
take precautions against anything else?
E
I don't like the idea of the as method as it cannot be used reasonable in
other circumstances as module loading. The code really does not belong into
class String IMHO.
We have Kernel#load [1] with a similar functionality already. Currently it
accepts an additional parameter to enforce wrapping of the file in an
anonymous module. That could be extended to accept a module as well; that
then would be the module used. Then we can do
load "foo.rb" # not wrapped
load "foo.rb" # wrapped in an anonymous module
load "foo.rb", MyExternal # wrapped in MyExternal
Additionally / Alternatively it could be useful to have a per module require
like this:
class Module
def require(*files)
# puts "loading in module #{self}: #{files.inspect}"
@loaded ||= {}
files.each do |f|
unless @loaded[f]
# real load code that wraps contents of f in self
@loaded[f] = true
end
end
end
end
Then we could do
module Foo
require "bar"
end
and even if we do the same later again, "bar" is only loaded once (see [2]).
Sounds like an RCR candidate, doesn't it?
Kind regards
robert
[1] http://www.ruby-doc.org/core/classes/Kernel.html#M001744
[2] http://www.ruby-doc.org/core/classes/Kernel.html#M001745
Wrapping is a nice idea (see raa:script for one approach), but the
wrapped lib will break if it depends on modifying classes or modules in
the global scope or defining global methods (the Complex example in my
previous email).
There is the idea the OP mentioned of parsing input files and rewriting
them to be explicit about the scope of definitions, automating the
kludgy hack I proposed for complex.rb. But that will break if the lib
uses dynamic code generation.
Maybe library writers should follow a standard of making their code
explicit about namespaces:
1. If you open and existing class, give an absolute path to it:
class ::Integer
rather than
class Integer
2. Global methods defs should be wrapped like this:
class ::Object
def global_meth ... end
end
3. References to constants (not just classes and modules) defined in the
lib should be relative:
class Foo
end
f = Foo.new
and not absolute:
f = ::Foo.new
(although I can't imagine that many libs have this problem).
4. Anything else to protect the lib from breakage when wrapping?
> > class String
> > def as mod
> > {:name => self, :module => mod}
> > end
> > end
> I don't like the idea of the as method as it cannot be used reasonable in
> other circumstances as module loading. The code really does not belong into
> class String IMHO.
Agreed. It just looked better :) I suppose a top-level method
would have done as nicely for this how-it-can-be-done-now
implementation.
> We have Kernel#load [1] with a similar functionality already. Currently it
> accepts an additional parameter to enforce wrapping of the file in an
> anonymous module. That could be extended to accept a module as well; that
> then would be the module used. Then we can do
>
> load "foo.rb" # not wrapped
> load "foo.rb" # wrapped in an anonymous module
> load "foo.rb", MyExternal # wrapped in MyExternal
Good. I had to rely on a 1.6 reference knowledge and didn't
recall the second parameter. Implementing it this way would,
certainly, require a change at the core level so an RCR might
be appropriate. I can write it up, I suppose.
I'm still partial to 'import' vs. 'require', though :)
> Additionally / Alternatively it could be useful to have a per module require
> like this:
>
> class Module
> def require(*files)
> # puts "loading in module #{self}: #{files.inspect}"
> @loaded ||= {}
> files.each do |f|
> unless @loaded[f]
> # real load code that wraps contents of f in self
> @loaded[f] = true
> end
> end
> end
> end
>
> Then we could do
>
> module Foo
> require "bar"
> end
>
> and even if we do the same later again, "bar" is only loaded once (see [2]).
Looks nice, although the functionality is somewhat
overlapping, no?
> Kind regards
>
> robert
E
Great!
> I'm still partial to 'import' vs. 'require', though :)
Well, I'm open there. It's just that load and require have established
semantics: "load" reads the file on every occurrence while "require" loads
it only once (plus it does some file name magice to determine whether it's
"foo.rb", or "foo.so" / "foo.dll").
> > Additionally / Alternatively it could be useful to have a per module
require
> > like this:
> >
> > class Module
> > def require(*files)
> > # puts "loading in module #{self}: #{files.inspect}"
> > @loaded ||= {}
> > files.each do |f|
> > unless @loaded[f]
> > # real load code that wraps contents of f in self
> > @loaded[f] = true
> > end
> > end
> > end
> > end
> >
> > Then we could do
> >
> > module Foo
> > require "bar"
> > end
> >
> > and even if we do the same later again, "bar" is only loaded once (see
[2]).
>
> Looks nice, although the functionality is somewhat
> overlapping, no?
Yes, that's why I wrote "Additionally / Alternatively". :-) But keep in
mind the difference between load once and load always.
Regards
robert
Totally agree. I guess this is seldom used these days.
> 2. Global methods defs should be wrapped like this:
>
> class ::Object
> def global_meth ... end
> end
That's really the same as case 1, isn't it?
> 3. References to constants (not just classes and modules) defined in the
> lib should be relative:
>
> class Foo
> end
>
> f = Foo.new
>
> and not absolute:
>
> f = ::Foo.new
>
> (although I can't imagine that many libs have this problem).
I think so, too.
> 4. Anything else to protect the lib from breakage when wrapping?
We would have to think about what happens if a wrapped module requires
another module. If that other module is a standard module you'll certainly
do not want it to be included in the wrapping namespace. If it belongs to
the same lib, then of course you want it wrapped. So the crucial question
is: how are these require's distinguished? I'm not 100% sure but I think a
simple 'require "foo"' will look relative as well as in global directories -
that might be a chance to distinguish...
As much as I like the original idea of wrapping classes in namespaces (much
the way that can be done with C++ because of #include) I believe this needs
more thought.
Kind regards
robert
I don't think anything will. Try this:
module Test
require 'thread'
end
m = Test::Mutex.new
Should cause an error.
E
There's probably a misunderstanding: I was not talking about actual behavior
but about the behavior of the suggested new include / require functionality
that was capable of wrapping loaded code in a module.
Regards
robert