Forum: Ruby override.rb

Posted by unknown (Guest)
on 2006-12-28 04:42
(Received via mailing list)
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
Posted by Paulo Köch (Guest)
on 2006-12-28 05:06
(Received via mailing list)
Holy cow!

Tell me, what do you want us to do with this, exactly? What are you
challenging us to? Didn't get it. =/
Posted by Trans (Guest)
on 2006-12-28 06:41
(Received via mailing list)
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
Posted by unknown (Guest)
on 2006-12-28 06:43
(Received via mailing list)
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. =/
>

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
Posted by Pit Capitain (Guest)
on 2006-12-28 08:57
(Received via mailing list)
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
Posted by Devin Mullins (twifkak)
on 2006-12-28 15:01
(Received via mailing list)
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?
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"
"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
Posted by unknown (Guest)
on 2006-12-28 17:42
(Received via mailing list)
On Thu, 28 Dec 2006, Pit Capitain wrote:

> 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
--
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
Posted by unknown (Guest)
on 2006-12-28 18:00
(Received via mailing list)
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 :-)  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
Posted by unknown (Guest)
on 2006-12-28 18:02
(Received via mailing list)
On Thu, 28 Dec 2006, Devin Mullins 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?
> 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?

>  p c.M(), c.m
>
> $ 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
--
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
Posted by unknown (Guest)
on 2006-12-28 18:06
(Received via mailing list)
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 :-)  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).

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
Posted by Trans (Guest)
on 2006-12-28 18:16
(Received via mailing list)
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?

T.
Posted by unknown (Guest)
on 2006-12-28 18:39
(Received via mailing list)
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?

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
Posted by Devin Mullins (twifkak)
on 2006-12-29 03:50
(Received via mailing list)
ara.t.howard@noaa.gov wrote:
> On Thu, 28 Dec 2006, Devin Mullins wrote:
>> 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
Posted by Trans (Guest)
on 2006-12-29 15:14
(Received via mailing list)
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?

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.
Posted by unknown (Guest)
on 2006-12-29 16:58
(Received via mailing list)
On Fri, 29 Dec 2006, Trans wrote:

> 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.

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

yes.  about 1 hour ago!  ;-)

> lurks in the background).
indeed.

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

more later....

-a
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.