ruby picture

RCR 289: Method to import a source file wrapped in a named module

Submitted by muir (Wed Dec 29 14:12:20 UTC 2004)

Abstract

Using modules to simulate globally unique namespaces is conceptually confusing and unnecessary. This problem can be solved by inverting the problem and making namespaces the responsibility of the client.

Problem

A perceived problem in writing and using third-party modules in programming is the possibility of namespace clashes. Most modern languages offer some implementation of the concept of 'namespace' in order to avoid such clashes. As an example, one can think of Java's package system where each package (namespace) is of the form tld.domain.package.path, e.g. com.foosoftware.math.logarithms, and of course Ruby's use of modules as namespaces.

The primary problem in having the library implementer establish the namespace for a certain project is guaranteeing the uniqueness of that namespace, i.e. ensuring that no other library is using the same designator. For this reason simple namespaces like 'math' are not practical and must be enclosed in a unique one like 'foosoftware'. This is not only cumbersome but also not guaranteed to be unique without having to go through some central authority (e.g. nothing in Java prevents me from using org.apache as my namespace even though I'm in no way affiliated with the organization).

The second problem, for Ruby particularly, is the conceptual muddling of 'module' that namespaces cause, one of the few instances where Ruby doesn't seem to be following the Principle of the Least Surprise. The implication of the word 'module' is of a functional/logical grouping ('module AllMathFunctions') instead of an associative grouping ('module AllFooSoftwareLibraries').

Proposal

Ruby should either augment the existing require() and load() methods to accept an additional optional argument of a named, possibly non-existing module and wrap the loaded code within that module in order to provide that loaded code with a namespace. Alternatively a new method import() could be used exclusively for this purpose. Instead of:  
 # Library code
 # foomath.rb
 module FooSoft
  module Math
   ...
  end
 end
 
 # Client code
 # test.rb
 require 'foomath'
 ...
 x = FooSoft::Math.do_math(y)

Code would be written:  

 # Library code
 # foomath.rb
 module Math
  ...
 end
 
 # Library code
 # barphys.rb
 module Phys
  ...
 end
 
 # Client code
 # test.rb
 import 'foomath', as :Extern
 import 'barphys'  # Phys doesn't clash 
 ...
 x = Extern::Math.do_math(y)
 z = Phys.physicate()

Analysis

Ruby should set itself apart from other languages and change the concept of namespaces to be something only the client programmer needs to worry about. As that programmer knows exactly what is already in the namespace of the program they're writing, they can decide whether a given library needs a separate namespace, and they can decide what to call the new namespace, which is good for consistency.

This change is, to a degree, possible to implement using current Ruby constructs (see Implementation). This, however, is not quite straightforward and involves some considerably slow code.

Additionally, since this change is the conceptual inverse of the traditional notion of a namespace as a library-writer's responsibility, it would be beneficial to include it at a language level to signal that this is the Ruby Way of doing namespaces.

There are certain (surpassable) problems with this approach (see: ), mainly the evaluation of 'top-level' code within the wrapped module--i.e., what happens if a given file changes the behaviour of one of the top-level modules or classes, String for example. If the entire file is simply wrapped in Extern, any modification of String would create a new class Extern::String. To avoid this problem, the loaded file should be transformed by escaping all of these top-level classes and modules before evaluating the file (unless, of course, the library IS implementing its own String class inside its namespace). Then a source file like this would have the following transformation:  

 # source.rb
 
 # Add a method to String
 class String
  def my_method
    ...
  end
 end
 
 # Some math library
 module Math
  # An internal String class (don't ask..)
  class String
   ...
  end
 end
 
 # result.rb
 # (not an actual file)
 
 #New namespace as given by the import()er
 module Extern
 
  # Add a method to String (escaped)
  class ::String
   ...
  end
 
  # Some math library
  module Math
   # Internal String class (not escaped)
   class String
    ...
   end
  end
 
  end # module Extern

However, this sort of processing being slow, it would work best as a builtin feature.

Implementation

 # A pseudo-implementation
 
 # Add methods at top level
 
 def as mod
   # Create the module in case it didn't exist
   Kernel.module_eval("module #{mod}; end")
   return Kernel.module_eval(mod.id2name)
 end
 
 def import file, mod
   fdata = IO.readlines file
   # Scan the file for top-level classes/modules
   fdata.each do |line|
    if line =~ /(class|module)\s+([A-Z].*?)/
      if $TOP_LEVEL_CLASSES_AND_MODULES.has_key? $2
        unless inside_a_module
          # Escape the module
        end
      else
        inside_a_module = true
      end
    end
    # Check for ending a library module here
   end
 
   # All scanned
   mod.module_eval(fdata)
 end

Alternatively, one could directly wrap any class/module that's not at top-level and leave the rest untouched. This does still leave the problem of detecting nested modules and classes.

ruby picture
Comments Current voting
I like the idea of importing a file as a named module. Could we use Kernel#load for this? At the moment
 load( "foo.rb",true)

wraps "foo.rb" in an anonymous module and it seem to me that it always returns true, or raises an exception. Maybe it could be possible to make it return the module itself, then the module would be named by just assigning it to a constant, which is what current ruby does. IMO this would be consistent with ruby, avoid a new method and still let what the author wants possible. As for the automatic renaming, I think it is a bad idea. An user could just use the module as usual if he wants it to be merged in the top level. -- GabrieleRenzi


oh, and remove the reference to POLS, thou should not name that in an RCR according to matz ;) -- GabrieleRenzi


Strongly opposed 0
Opposed 0
Neutral 1
In favor 1
Strongly advocate 4
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .