Override.rb

since the threads split all over the place i figured i'd start yet another.
can people break this?

harp:~ > cat a.rb
class Module
   module OverRiding
     class << self
       def new
         Module.new{
           @added_methods = []
           @parent = nil

           class << self
             attr :added_methods
             attr :parent

             def method_added m
               super if defined? super
             ensure
               added_methods << m.to_s
             end

             def remove_method m
               super if defined? super
             ensure
               added_methods.delete_at(added_methods.rindex(m.to_s)) rescue nil
             end

             def add_parent m
               include(@parent = m)
             end

             def cache mname, m
               mname = mname.to_s
               const = mname.gsub(%r/[!]/,'__bang__').gsub(%r/[?]/,'__what__').upcase
               const_set const, m

               module_eval <<-code
                 def #{ mname } *a, &b
                   #{ const }.bind(self).call *a, &b
                 end
               code
             end

             def walking_up
               m = self
               loop{
                 yield m
                 break unless((m=m.parent) rescue nil)
               }
             end
           end
         }
       end
     end
   end

   def overriding &b
     this = self
     m = OverRiding.new
     m.module_eval &b

     unless defined? @overriding
       @overriding = OverRiding.new
       m.added_methods.each do |mname|
         @overriding.cache mname, this.instance_method(mname)
         this.remove_method mname
       end
     end

     m.add_parent @overriding
     include m
     @overriding = m
   end
   alias_method 'override', 'overriding'

   def restore mname
     if defined? @overriding
       mname = mname.to_s
       @overriding.walking_up do |m|
         break( m.remove_method mname ) if m.added_methods.include?(mname)
       end
     end
     instance_method mname
   end

   def restoring *a, &b
     o = Object.new
     sc = class << o; self; end
     sc.module_eval{
       instance_methods.each{|m| remove_method m unless m[/^__/] rescue nil}
       define_method('method_missing'){|m, *a| a << m }
     }
     o.instance_eval &b
     a.flatten.compact.each{|m| restore m}
   end
end

if $0 == __FILE__
   class C
     c = new

     def m() 'a' end
     p c.m #=> 'a'

     override{ def m() super + 'b' end }
     p c.m #=> 'ab'

     override{ def m() super + 'c' end }
     p c.m #=> 'abc'

     override{ def m() super + 'd' end }
     p c.m #=> 'abcd'

     restore 'm'
     p c.m #=> 'abc'

     restore 'm'
     p c.m #=> 'ab'

     restore 'm'
     p c.m #=> 'a'
   end
end

harp:~ > ruby a.rb
"a"
"ab"
"abc"
"abcd"
"abc"
"ab"
"a"

-a

···

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

Holy cow!

Tell me, what do you want us to do with this, exactly? What are you
challenging us to? Didn't get it. =/

···

On 12/28/06, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> wrote:

since the threads split all over the place i figured i'd start yet another.
can people break this?

harp:~ > cat a.rb
class Module
   module OverRiding
     class << self
       def new
         Module.new{
           @added_methods =
           @parent = nil

           class << self
             attr :added_methods
             attr :parent

             def method_added m
               super if defined? super
             ensure
               added_methods << m.to_s
             end

             def remove_method m
               super if defined? super
             ensure
               added_methods.delete_at(added_methods.rindex(m.to_s)) rescue nil
             end

             def add_parent m
               include(@parent = m)
             end

             def cache mname, m
               mname = mname.to_s
               const = mname.gsub(%r/[!]/,'__bang__').gsub(%r/[?]/,'__what__').upcase
               const_set const, m

               module_eval <<-code
                 def #{ mname } *a, &b
                   #{ const }.bind(self).call *a, &b
                 end
               code
             end

             def walking_up
               m = self
               loop{
                 yield m
                 break unless((m=m.parent) rescue nil)
               }
             end
           end
         }
       end
     end
   end

   def overriding &b
     this = self
     m = OverRiding.new
     m.module_eval &b

     unless defined? @overriding
       @overriding = OverRiding.new
       m.added_methods.each do |mname|
         @overriding.cache mname, this.instance_method(mname)
         this.remove_method mname
       end
     end

     m.add_parent @overriding
     include m
     @overriding = m
   end
   alias_method 'override', 'overriding'

   def restore mname
     if defined? @overriding
       mname = mname.to_s
       @overriding.walking_up do |m|
         break( m.remove_method mname ) if m.added_methods.include?(mname)
       end
     end
     instance_method mname
   end

   def restoring *a, &b
     o = Object.new
     sc = class << o; self; end
     sc.module_eval{
       instance_methods.each{|m| remove_method m unless m[/^__/] rescue nil}
       define_method('method_missing'){|m, *a| a << m }
     }
     o.instance_eval &b
     a.flatten.compact.each{|m| restore m}
   end
end

if $0 == __FILE__
   class C
     c = new

     def m() 'a' end
     p c.m #=> 'a'

     override{ def m() super + 'b' end }
     p c.m #=> 'ab'

     override{ def m() super + 'c' end }
     p c.m #=> 'abc'

     override{ def m() super + 'd' end }
     p c.m #=> 'abcd'

     restore 'm'
     p c.m #=> 'abc'

     restore 'm'
     p c.m #=> 'ab'

     restore 'm'
     p c.m #=> 'a'
   end
end

harp:~ > ruby a.rb
"a"
"ab"
"abc"
"abcd"
"abc"
"ab"
"a"

-a
--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

Ara wrote:

since the threads split all over the place i figured i'd start yet another.
can people break this?

Interesting. I'll take a closer look at this tomorrow. In some ways it
is similar to #instance_intercept which I've been working on:

# = Instance Interception

···

#
# This code is in the spirit of class_extension, but performs instance
# level method interception instead of class level method inheritance.

class Module

  def instance_interception(&block)
    @instance_interception ||= Module.new do
      def self.append_features(mod)
        append_features_without_instance_interception( mod )
      end
    end
    @instance_interception.module_eval(&block) if block_given?
    @instance_interception
  end

  private :instance_interception

  alias_method :append_features_without_instance_interception,
:append_features

  # Append features

  def append_features( mod )

    aspect = instance_interception
    aspect.__send__( :append_features_without_instance_interception,
mod )

    aspect.instance_methods.each do |meth|
      if mod.method_defined?( meth )
        aspect.advise( mod, meth )
      end
    end

    append_features_without_instance_interception( mod )

    #if mod.instance_of? Module
    aspect.__send__( :append_features_without_instance_interception,
mod.__send__(:instance_interception) )
    #end

  end

  # Apply the around advice.

  def advise( mod, meth )
    advice = instance_method( meth )
    instance_target = mod.instance_method(meth)
    mod.__send__( :define_method, meth ) { |*args| #, &blk|
      target = instance_target.bind( self )
      (class << target; self; end).class_eval { define_method( :super
){ call( *args ) } }
      advice.bind( self ).call( target, *args ) #, &blk )
    }
  end

  # TODO make method_added hook more robust so not aas to clobber
others.
  # If a method is added to the module/class that is advised.

  def method_added( meth )
    return if @method_added_short
    if instance_interception.method_defined?( meth )
      include instance_interception
      @method_added_short = true
      instance_interception.advise( self, meth )
      @method_added_short = false
    end
  end

end

=begin test

  require 'test/unit'

  class TestModule < Test::Unit::TestCase

    module A

      def f ; "F" ; end
      def g ; "G" ; end

      instance_interception do
        def f( target, *args, &blk )
          '{' + target.super + '}'
        end
        def g( target, *args, &blk )
          '{' + target.super + '}'
        end
      end

    end

    class X
      def f ; super ; end
      include A
      def g ; super ; end
    end

    def test_1_01
      x = X.new
      assert_equal( "{F}", x.f )
      assert_equal( "{G}", x.g )
    end

  end

=end

ara.t.howard@noaa.gov schrieb:

(... code to override and restore methods ...)

Ara, though I find this very interesting I've not much time to figure it out myself, so could you please show us an example with #overriding and #restoring ?

can people break this?

What do you mean by "break"?

Regards,
Pit

well... yes! i ran it, and got:
"a"
a.rb:63:in `override': private method `remove_method' called for C:Class (NoMethodError)
         from a.rb:61:in `each'
         from a.rb:61:in `override'
         from a.rb:102

then, once I fixed that:

$ cat b.rb
require 'a'

class C
   c = new

   def M; 'a' end
   def m; 'A' end
   p c.M(), c.m

   override { def M; super + 'b' end; def m; super + 'B' end }
   p c.M(), c.m

   restore 'M'
   p c.M(), c.m

   restore 'm'
   p c.M(), c.m
end

$ ruby b.rb
"a"
"A"
./a.rb:32: warning: already initialized constant M
"Ab"
"AB"
"A"
"AB"
"A"

This code's a lot longer than the last one you sent, so it's gonna take me more time to figure it out.

Devin

···

ara.t.howard@noaa.gov wrote:

since the threads split all over the place i figured i'd start yet another.
can people break this?

Aren't you leaving residual emtpy modules behind when you ermove
methods?

T.

···

ara.t.howard@noaa.gov wrote:

since the threads split all over the place i figured i'd start yet another.
can people break this?

[snip code]

it is a candidate release which addresses both these threads:

   http://groups-beta.google.com/group/comp.lang.ruby/browse_frm/thread/a80cd271e6896aab/8f728cbad498d432?lnk=raot#8f728cbad498d432

   http://groups-beta.google.com/group/comp.lang.ruby/browse_frm/thread/3f94b778d65045be/a25b7705ea5da80f#a25b7705ea5da80f

afaikt it it the most flexible and complete solution. unless someone points
out a flaw i'll gem it up tomorrow.

regards.

-a

···

On Thu, 28 Dec 2006, [UTF-8] Paulo Köch wrote:

Holy cow!

Tell me, what do you want us to do with this, exactly? What are you
challenging us to? Didn't get it. =/

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

ara.t.howard@noaa.gov schrieb:

(... code to override and restore methods ...)

Ara, though I find this very interesting I've not much time to figure it out myself, so could you please show us an example with #overriding and #restoring ?

   harp:~ > cat a.rb
   require 'override'

   class C
     c = new

     def m() 'a' end
     def n() 'x' end

     p c.m #=> a
     p c.n #=> x

     overriding{
       def m() super << 'b' end
       def n() super << 'y' end
     }

     p c.m #=> ab
     p c.n #=> xy

     restoring{ m and n }

     p c.m #=> a
     p c.n #=> x
   end

   harp:~ > ruby a.rb
   "a"
   "x"
   "ab"
   "xy"
   "a"
   "x"

i'll probably merge restore/restoring so the above will become

   restore{ m and n }

can people break this?

What do you mean by "break"?

just finding bugs.

regards.

ps. latest version inlined below

-a

override.rb (2.63 KB)

···

On Thu, 28 Dec 2006, Pit Capitain wrote:
--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

file: override.rb

class Module
   def singleton_class &b
     @singleton_class ||= (class << self; self; end)
     @singleton_class.module_eval &b if b
     @singleton_class
   end
   def overload :pattern => :method
     singleton_class{
       module_eval <<-co
     }
   end
end

class C
   def self.new0 i
     p [i]
   end
   def self.new1 i, s
     p [i,s]
   end
   def self.new2 i, s, f
     p [i,s,f]
   end
end

since the threads split all over the place i figured i'd start yet another.
can people break this?

well... yes! i ran it, and got:
"a"
a.rb:63:in `override': private method `remove_method' called for C:Class (NoMethodError)
       from a.rb:61:in `each'
       from a.rb:61:in `override'
       from a.rb:102

how odd. which ruby version?

then, once I fixed that:

$ cat b.rb
require 'a'

class C
c = new

def M; 'a' end
def m; 'A' end
p c.M(), c.m

override { def M; super + 'b' end; def m; super + 'B' end }
p c.M(), c.m

restore 'M'
p c.M(), c.m

restore 'm'
p c.M(), c.m
end

$ ruby b.rb
"a"
"A"
./a.rb:32: warning: already initialized constant M

hmmm. i reworked to use a single constant table of methods.

latest below and attached.

-a

override.rb (2.71 KB)

···

On Thu, 28 Dec 2006, Devin Mullins wrote:

ara.t.howard@noaa.gov wrote:

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

class Module
   module OverRiding
     class << self
       def new
         Module.new{
           @added_methods =
           @parent = nil

           class << self
             attr :added_methods
             attr :parent

             def method_added m
               super if defined? super
             ensure
               added_methods << m.to_s
             end

             def delete_method m
               remove_method m
               added_methods.delete_at(added_methods.rindex(m.to_s))
             end

             def add_parent m
               include(@parent = m)
             end

             def cache mname, m
               mname = mname.to_s
               const_set :METHODS____, {} unless const_defined? :METHODS____
               const_get(:METHODS____)[ mname ] = m
               module_eval <<-code
                 def #{ mname } *a, &b
                   METHODS____[ '#{ mname }' ].bind(self).call *a, &b
                 end
               code
             end

             def walking_up
               m = self
               loop{
                 yield m
                 break unless((m=m.parent) rescue nil)
               }
             end
           end
         }
       end
     end
   end

   def overriding &b
     this = self
     m = OverRiding.new
     m.module_eval &b

     unless defined? @overriding
       @overriding = OverRiding.new
       m.added_methods.each do |mname|
         @overriding.cache mname, this.instance_method(mname)
         remove_method mname
       end
     end

     m.add_parent @overriding
     include m
     @overriding = m
   end
   alias_method 'override', 'overriding'

   def restoring *_a, &_b
     if _b # barewords will restore methods
       o = Object.new
       sc = class << o; self; end
       sc.module_eval{
         instance_methods.each{|m| remove_method m unless m[/^__/] rescue nil}
         define_method('method_missing'){|__m, *__a| _a << __m; self }
       }
       o.instance_eval &_b
     end
     _a.flatten.compact.map do |mname|
       if defined? @overriding
         mname = mname.to_s
         @overriding.walking_up do |m|
           break( m.delete_method mname ) if m.added_methods.include?(mname)
         end
       end
       instance_method mname
     end
   end
   alias_method 'restore', 'restoring'
end

if $0 == __FILE__

   class C
     c = new

     def m() 'a' end
     p c.m #=> 'a'

     override{ def m() super + 'b' end }
     p c.m #=> 'ab'

     override{ def m() super + 'c' end }
     p c.m #=> 'abc'

     override{ def m() super + 'd' end }
     p c.m #=> 'abcd'

     restore 'm'
     p c.m #=> 'abc'

     restore 'm'
     p c.m #=> 'ab'

     restore 'm'
     p c.m #=> 'a'
   end

end

not really. basically the first call to override sets up a master parent
module with the orginal methods

   object -> [master]

and re-definition takes place in a module between the object and this master,
thus super works

   object -> [redef] -> [master]

subsquent redefs chain like so, preserving super semantics

   object -> [redef 2] -> [redef 1] -> [master]

note that the modules are created not once per method, but once per call to
'override'. therefore this

   class C
     def a() 'a' end
     def b() 'b' end
     def c() 'c' end

     override{
       def a() super.upcase end
       def b() super.upcase end
       def c() super.upcase end
     }
   end

creates exactly two modules: the master and one redef. now if we do this

   class C
     override{
       def a() super.downcase end
       def b() super.downcase end
       def c() super.downcase end
     }
   end

we've created just one more. something like

   object -> [redef 'downcase'] -> [redef 'upcase'] -> [master]

now, say we do this

   class C
     restore{
       a and b
     }
   end

then __only__ the methods 'a' and 'b' from [redef 'downcase'] are removed.
the 'c' method still lives there.

so, while it is a true statement that __each__ call to 'override' introduces a
new mixin module, it is not true that __every__ call to 'restore' leaves an
extraneous module mixed in, though it's possible that a given call to
'restore' may indeed have that effect.

make sense?

-a

···

On Fri, 29 Dec 2006, Trans wrote:

ara.t.howard@noaa.gov wrote:

since the threads split all over the place i figured i'd start yet another.
can people break this?

[snip code]

Aren't you leaving residual emtpy modules behind when you ermove
methods?

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

Hi --

···

On Fri, 29 Dec 2006, ara.t.howard@noaa.gov wrote:

Whoops, it's one of those messages that won't quote back the text. Oh
well :slight_smile: All I wanted to ask was: do you think 'override' is the best
name for this? The reason I ask is that "require 'override'" sounds
like you're importing override ability into Ruby, without any
indication of the enhancements that the package provides (i.e., the
restoring ability).

David

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
    (See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

a.rb:63:in `override': private method `remove_method' called for

how odd. which ruby version?

ruby 1.8.5 (2006-08-25) [i386-mswin32]

(Yes, the "$" prompt in my last email was a lie. Mischevious!)

hmmm. i reworked to use a single constant table of methods.

Will take a look. Looks to be cool-in-progress.

Devin

···

ara.t.howard@noaa.gov wrote:

On Thu, 28 Dec 2006, Devin Mullins wrote:

sure, it makes sense, but you do have that possibility. and i agree
it's not such a big deal. just pointing it out. i also point out that
the idea of override.rb is but a step from the idea of cuts. in fact
implementation of overide using cuts is little more than:

  def override &block
    Cut.new(self,&block)
  end

and my hacked-up pure ruby implementation of cuts is very similar in
design that of override.rb.

moving slightly off-topic. have you ever thought about the possibility
of every method being as if it were it's own module?

  class X

    module A
      def a; "a"; end
    end
    include A

    module B
      def b; "b"; end
    end
    include B

  end

the ablity to dynamically manipulate behavior goes way up.

  class X
    module A2
      def a; '{'+super+'}'; end
    end
    include A2
  end

of course so does the overhead. (and the dynamic inclusion problem
still lurks in the background).

t.

···

ara.t.howard@noaa.gov wrote:

so, while it is a true statement that __each__ call to 'override' introduces a
new mixin module, it is not true that __every__ call to 'restore' leaves an
extraneous module mixed in, though it's possible that a given call to
'restore' may indeed have that effect.

make sense?

i'm not attached to the name at all. suggestions? whatever you come up with
should imply:

   "the following block allows one to redefine methods in a functional way:
   which is to say 'super' will be scoped in any method re-definition so as to
   refer to the 'current' method of the same name"

the method can be used multiple time in a stack-like way with 'restore'
affecting the inverse operation.

i want to avoid any 'stack-like' names since the inability to un-include a
module in ruby and the fact that this impl is module inclusion based would be
misleading. the impl sets up a sort of linked list of included/supered
modules to provide the call up stack for nested supers.

regards.

-a

···

On Fri, 29 Dec 2006 dblack@wobblini.net wrote:

Hi --

On Fri, 29 Dec 2006, ara.t.howard@noaa.gov wrote:

Whoops, it's one of those messages that won't quote back the text. Oh
well :slight_smile: All I wanted to ask was: do you think 'override' is the best
name for this? The reason I ask is that "require 'override'" sounds
like you're importing override ability into Ruby, without any
indication of the enhancements that the package provides (i.e., the
restoring ability).

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

sure, it makes sense, but you do have that possibility. and i agree
it's not such a big deal. just pointing it out. i also point out that
the idea of override.rb is but a step from the idea of cuts. in fact
implementation of overide using cuts is little more than:

def override &block
   Cut.new(self,&block)
end

and my hacked-up pure ruby implementation of cuts is very similar in
design that of override.rb.

i confess cuts are a bit heavyweight for my tastes. however - i aggree that
the effect and even the impl to some respects is very similar.

moving slightly off-topic. have you ever thought about the possibility
of every method being as if it were it's own module?

class X

   module A
     def a; "a"; end
   end
   include A

   module B
     def b; "b"; end
   end
   include B

end

yes. about 1 hour ago! :wink:

the ablity to dynamically manipulate behavior goes way up.

class X
   module A2
     def a; '{'+super+'}'; end
   end
   include A2
end

of course so does the overhead. (and the dynamic inclusion problem still
lurks in the background).

indeed.

no you've done it - my new project is in the works!

more later....

-a

···

On Fri, 29 Dec 2006, Trans wrote:
--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama