Alias/method redfinition

it seems alias/method redefing can be a slipery slope. i wanted to
make the Array class indexable by field name for some postgres work i
was doing. i use the following method of redefining the [] and []=
methods of the Array class, can any one comment on the effectiveness
of this technique?

USAGE :

tuple = [0,1,2]
tuple.fields = [‘zero’,‘one’,'two]

puts tuple[‘zero’] -> 0
puts tuple[‘one’] -> 1
puts tuple[‘two’] -> 2

IMPLEMENTATION :

class Array
def fields=(*fields)
@fields = fields.flatten
@fieldpos = Hash.new
@fields.each_with_index {|f,i| @fieldpos[f] = i}

# redef only once!
if not @oldcalls
  @oldcalls = {
	    '[]'   => method('[]'),
	    '[]='  => method('[]='),
	  };

  m = Module.new
  m.module_eval %q{
def [] idx 
  if @fields and idx.type == String
    pos = @fieldpos[idx]
    return nil unless pos
    self[pos]
  else
    @oldcalls['[]'].call idx
  end
end
def []= idx, value 
  if @fields and idx.type == String
    pos = @fieldpos[idx]
    if not pos
      pos = size
      @fieldpos[idx] = pos
    end
    self[pos] = value
  else
    @oldcalls['[]='].call idx, value 
  end
end
  }
  self.extend m
end

end
attr :fields
attr :fieldpos
end

implementation :

      m = Module.new
      m.module_eval %q{

You use #eval which is evil

   module M
      def idx
         if @fields and idx.type == String
            pos = @fieldpos[idx]
            return nil unless pos
            self[pos]
         else
            super
         end
      end
      def = idx, value
         if @fields and idx.type == String
            pos = @fieldpos[idx]
            if not pos
               pos = size
               @fieldpos[idx] = pos
            end
            self[pos] = value
         else
            super
         end
      end
   end
   
   class Array
      def fields=(*fields)
         extend M unless @fields
         @fields = fields.flatten
         @fieldpos = Hash.new
         @fields.each_with_index {|f,i| @fieldpos[f] = i}
      end
      attr_reader :fields
      attr_reader :fieldpos
   end

Guy Decoux

ts decoux@moulon.inra.fr wrote in message news:200209191714.g8JHEeb09103@moulon.inra.fr

You use #eval which is evil

if the eval’d string is constant, why?

[snip]

  def [] idx 
     if @fields and idx.type == String
        pos = @fieldpos[idx]
        return nil unless pos
        self[pos]
     else
        super
     end
  end

this works. though i do not understand how the super remains bound to
the old after the call ‘extend M’

-a

ts decoux@moulon.inra.fr wrote in message news:200209191714.g8JHEeb09103@moulon.inra.fr

You use #eval which is evil

if the eval’d string is constant, why?

There are 2 problems in your implementation

  1. you use #module_eval to compile and execute a String which never
    change, and this each time that #fields is called.

  2. to use #module_eval you create a new anonymous module and because you
    don’t want to have multiple include for the same object you need to
    protect the call with @oldcalls

These 2 problems can be easily solved with a module

  1. the module is compiled only once (when ruby compile the script) and
    not for each call of #fields

  2. ruby never try to include twice the same module, This mean that in
    #fields you can just write

    def fields=(*fields)
    extend M
    # etc
    end

If the module was previously included ruby do nothing otherwise it
execute the include

If I’ve written `extend M unless @fields’ this is just to make a little
faster (i.e. don’t try to call #extend if ruby do nothing)

You have one case where #module_eval create more problems than it solve

this works. though i do not understand how the super remains bound to
the old after the call ‘extend M’

The new methods # and #= are created in the module M and they don’t
erase the old definitions which are in the class Hash

When you write

extend M

ruby will create a proxy class which is inserted just before the class
Hash, this will give a class (and methods) specific to this object

When you call # (#=) for this object, ruby find these methods in the
proxy class, and with a call to #super ruby will retrieve the methods
stored in Hash

You have something like this

  • before extend, the inheritance for the object is

Hash ==> [Enumerable] ==> Object ==> [Kernel]

  • after extend for this same object

(singleton class) ==> [M] ==> Hash ==> [Enumerable] ==> Object ==> [Kernel]

[M] is a proxy class to M
[Enumerable] is a proxy class to Enumerable

···

Guy Decoux

ts decoux@moulon.inra.fr wrote in message news:rfcsn01ouju.fsf@moulon.inra.fr

so, in general, extending an object always results in a new proxy
class being created as the first one to be searched in the inheritence
hierarchy, and because mutiply extending an object with a module would
result in duplicate classes being created in the inheritence tree ruby
protects against this by
not allowing ‘extend M’ to happen more than once?

-a

···

this works. though i do not understand how the super remains bound to
the old after the call ‘extend M’

The new methods # and #= are created in the module M and they don’t
erase the old definitions which are in the class Hash

When you write

extend M

ruby will create a proxy class which is inserted just before the class
Hash, this will give a class (and methods) specific to this object

When you call # (#=) for this object, ruby find these methods in the
proxy class, and with a call to #super ruby will retrieve the methods
stored in Hash

You have something like this

  • before extend, the inheritance for the object is

Hash ==> [Enumerable] ==> Object ==> [Kernel]

  • after extend for this same object

(singleton class) ==> [M] ==> Hash ==> [Enumerable] ==> Object ==> [Kernel]

[M] is a proxy class to M
[Enumerable] is a proxy class to Enumerable

so, in general, extending an object always results in a new proxy
class being created as the first one to be searched in the inheritence
hierarchy,

second one : first ruby create a singleton class for example

pigeon% cat b.rb
#!/usr/bin/ruby
class A
   def a
      p "class A"
   end
end

module M
   def a
      p "module M"
      super
   end
end

a = A.new
a.extend M
class << a
   def a
      p "singleton class"
      super
   end
end

a.a
pigeon%

pigeon% b.rb
"singleton class"
"module M"
"class A"
pigeon%

and because mutiply extending an object with a module would
result in duplicate classes being created in the inheritence tree ruby
protects against this by
not allowing 'extend M' to happen more than once?

yes,

Guy Decoux