Using an UnboundMethod instead of an alias to redefine a method

Hi all,

I came across a technique for aliasing methods that I have never seen
before [1] and was just too good to pass up. But first, the setup.

If you want to redefine a method of an existing class, the traditional
approach is to alias the method first, then call the alias as needed.
For example, if you want to redefine Hash#[]= so that it stores
multiple values as a list instead of overwriting the existing value you
might do this:

class Hash
   alias :old_hset :[]=

   def []=(key, value)
      if self[key]
         self[key] << value
      else
         self.old_hset(key, [value])
      end
   end
end

hash = {}
hash['a'] = 1
hash['a'] = 2
hash['a'] # [1,2]

That works well enough. But, it's not ideal. Why? First, because now
you've got an aliased method laying around that we really don't want
exposed to the public. Second, this could cause a problem if anyone
were to ever define an old_hset method or alias themselves. Unlikely,
but possible.

The solution that Martin came up with is to use an UnboundMethod like
so:

class Hash
   hset = self.instance_method(:[]=)

   define_method(:[]=) do |key, value|
      if self[key]
         self[key] << value
      else
         hset.bind(self).call(key, [value])
      end
   end
end

So, now our custom Hash#[]= method is bound to an UnboundMethod that no
one else has access to (see the first link below for a better
explanation). Pretty neat, eh? Is there any downside to this approach?
If not, it seems this technique ought to be filed under 'best
practices', and perhaps even abstracted somehow in the core itself.

Many thanks to Jay Fields [2], whose blog entry led me back to the
original entry by Martin Traverso.

Regards,

Dan

[1] http://split-s.blogspot.com/2006/01/replacing-methods.html
[2]
http://jayfields.blogspot.com/2006/12/ruby-alias-method-alternative.html

The solution that Martin came up with is to use an UnboundMethod like
so:

class Hash
  hset = self.instance_method(:=)

  define_method(:=) do |key, value|
     if self[key]
        self[key] << value
     else
        hset.bind(self).call(key, [value])
     end
  end
end

here, however, you've lost any block that goes with the method ;-(

So, now our custom Hash#= method is bound to an UnboundMethod that no
one else has access to (see the first link below for a better
explanation). Pretty neat, eh? Is there any downside to this approach?
If not, it seems this technique ought to be filed under 'best
practices', and perhaps even abstracted somehow in the core itself.

there are several problems related to scoping:

http://groups-beta.google.com/group/comp.lang.ruby/browse_frm/thread/601ee261a57d7e57/09a22a5ca639834f?lnk=gst&q=harp+push_method&rnum=1#09a22a5ca639834f

regards.

-a

···

On Wed, 27 Dec 2006, Daniel Berger 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

Daniel Berger schrieb:

I came across a technique for aliasing methods that I have never seen
before [1] and was just too good to pass up. (...)

Dan, this is a nice technique indeed (not new, but still nice), but it comes with a performance penalty:

   class HashUsingAlias < Hash
      alias :old_hset :=

      def =(key, value)
        self.old_hset(key, value)
      end
   end

   class HashUsingBind < Hash
      hset = self.instance_method(:=)

      define_method(:=) do |key, value|
        hset.bind(self).call(key, value)
      end
   end

   require "benchmark"

   def bm_report bm, title, hash_class
     hash = hash_class.new
     bm.report title do
       100_000.times do
         hash[ 1 ] = 1
       end
     end
   end

   Benchmark.bmbm do |bm|
     bm_report bm, "original", Hash
     bm_report bm, "alias", HashUsingAlias
     bm_report bm, "bind", HashUsingBind
   end

On my system, I get the following results:

                  user system total real
   original 0.062000 0.000000 0.062000 ( 0.062000)
   alias 0.141000 0.000000 0.141000 ( 0.140000)
   bind 0.656000 0.000000 0.656000 ( 0.657000)

Regards,
Pit

I came across a technique for aliasing methods that I have never seen
before [1] and was just too good to pass up. But first, the setup.

[...]

So, now our custom Hash#= method is bound to an UnboundMethod that no
one else has access to (see the first link below for a better
explanation). Pretty neat, eh? Is there any downside to this approach?
If not, it seems this technique ought to be filed under 'best
practices', and perhaps even abstracted somehow in the core itself.

Many thanks to Jay Fields [2], whose blog entry led me back to the
original entry by Martin Traverso.

That technique is fairly old (I myself tried to popularize it a few years
ago). It's cleaner & generally safer than alias_method, but there are three
shortcomings:
* the environment captured by the closure is often too heavy (extra care
  needed to get rid of unwanted references is needed)
* a method defined with define_method+block cannot take a block under 1.8
  (it's possible in 1.9, though)
* method calls are much slower

[1] some other examples
    http://thekode.net/ruby/techniques/CapturingMethods.html

···

On Wed, Dec 27, 2006 at 11:30:05PM +0900, Daniel Berger wrote:
--
Mauricio Fernandez - http://eigenclass.org - singular Ruby

mine is slowest of all, however it preserves blocks: see if you can speed it
up:

harp:~ > cat a.rb
class HashUsingAlias < Hash
    alias :old_hset :=

    def =(key, value)
      self.old_hset(key, value)
    end
end

class HashUsingBind < Hash
    hset = self.instance_method(:=)

    define_method(:=) do |key, value|
      hset.bind(self).call(key, value)
    end
end

require 'override'
class HashUsingOverride < Hash
   override('='){ def =(k,v) super end }
end

require "benchmark"
def bm_report bm, title, hash_class
   hash = hash_class.new
   bm.report title do
     100_000.times do
       hash[ 1 ] = 1
     end
   end
end

Benchmark.bmbm do |bm|
   bm_report bm, "original", Hash
   bm_report bm, "alias", HashUsingAlias
   bm_report bm, "bind", HashUsingBind
   bm_report bm, "override", HashUsingOverride
end

harp:~ > ruby a.rb
Rehearsal --------------------------------------------
original 0.070000 0.000000 0.070000 ( 0.070856)
alias 0.140000 0.000000 0.140000 ( 0.144095)
bind 0.370000 0.000000 0.370000 ( 0.381127)
override 0.470000 0.000000 0.470000 ( 0.476067)
----------------------------------- total: 1.050000sec

                user system total real
original 0.070000 0.000000 0.070000 ( 0.072046)
alias 0.150000 0.000000 0.150000 ( 0.144368)
bind 0.390000 0.000000 0.390000 ( 0.388440)
override 0.470000 0.000000 0.470000 ( 0.481620)

harp:~ > cat override.rb
class Module
   def child this = self
     @child ||= self.class.new
     @child.module_eval{ include this}
     @child
   end

   def has_child
     defined? @child and @child
   end

   def override m, &b
     this = self

     m = Module.new{
       @m = this.instance_method m
       this.module_eval{ remove_method m rescue nil }

       module_eval <<-code
         def #{ m }(*a, &b)
           um = ObjectSpace._id2ref #{ @m.object_id }
           um.bind(self).call *a, &b
         end
       code

       child.module_eval &b if b
     }

     include(m.has_child ? m.child : m)
   end
end

-a

···

On Thu, 28 Dec 2006, Pit Capitain wrote:

Daniel Berger schrieb:

I came across a technique for aliasing methods that I have never seen
before [1] and was just too good to pass up. (...)

Dan, this is a nice technique indeed (not new, but still nice), but it comes with a performance penalty:

class HashUsingAlias < Hash
    alias :old_hset :=

    def =(key, value)
      self.old_hset(key, value)
    end
end

class HashUsingBind < Hash
    hset = self.instance_method(:=)

    define_method(:=) do |key, value|
      hset.bind(self).call(key, value)
    end
end

require "benchmark"

def bm_report bm, title, hash_class
   hash = hash_class.new
   bm.report title do
     100_000.times do
       hash[ 1 ] = 1
     end
   end
end

Benchmark.bmbm do |bm|
   bm_report bm, "original", Hash
   bm_report bm, "alias", HashUsingAlias
   bm_report bm, "bind", HashUsingBind
end

On my system, I get the following results:

                user system total real
original 0.062000 0.000000 0.062000 ( 0.062000)
alias 0.141000 0.000000 0.141000 ( 0.140000)
bind 0.656000 0.000000 0.656000 ( 0.657000)

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

Mauricio Fernandez wrote:

That technique is fairly old (I myself tried to popularize it a few years
ago). It's cleaner & generally safer than alias_method, but there are three
shortcomings:
* the environment captured by the closure is often too heavy (extra care
  needed to get rid of unwanted references is needed)
* a method defined with define_method+block cannot take a block under 1.8
  (it's possible in 1.9, though)
* method calls are much slower

[1] some other examples
    http://thekode.net/ruby/techniques/CapturingMethods.html

(* Trans still patiently awaits Cuts *)

T.

Mauricio Fernandez wrote:

[1] some other examples
    http://thekode.net/ruby/techniques/CapturingMethods.html

To slightly hijack this thread (only slightly, since overriding methods
is being discussed)...do any of you smart participants know the answer
to the thread I posted Christmas eve, titled "Method equality; setting
instance variables on Method instances"?

http://groups.google.com/group/comp.lang.ruby/browse_frm/thread/951b2a42731ab66b

I suspect I'm not coming up with anything better than the other methods
discussed here, but it grates on me that I can't figure out what's
going on.

Mauricio Fernandez wrote:

> I came across a technique for aliasing methods that I have never seen
> before [1] and was just too good to pass up. But first, the setup.
[...]
> So, now our custom Hash#= method is bound to an UnboundMethod that no
> one else has access to (see the first link below for a better
> explanation). Pretty neat, eh? Is there any downside to this approach?
> If not, it seems this technique ought to be filed under 'best
> practices', and perhaps even abstracted somehow in the core itself.
>
> Many thanks to Jay Fields [2], whose blog entry led me back to the
> original entry by Martin Traverso.

That technique is fairly old (I myself tried to popularize it a few years
ago). It's cleaner & generally safer than alias_method, but there are three
shortcomings:
* the environment captured by the closure is often too heavy (extra care
  needed to get rid of unwanted references is needed)

How can we lighten it, if at all?

* a method defined with define_method+block cannot take a block under 1.8
  (it's possible in 1.9, though)

Hm, that is a limitation, but at least the simple cases still work.

* method calls are much slower

Yes, I saw some of the benchmarks. Maybe we could memoize the binding
somehow? I don't know if that even makes sense. I'm just tossing ideas
out there.

I guess for now I'll live with making my private aliases...private. :slight_smile:

Regards,

Dan

···

On Wed, Dec 27, 2006 at 11:30:05PM +0900, Daniel Berger wrote:

Phrogz schrieb:

To slightly hijack this thread (only slightly, since overriding methods
is being discussed)...do any of you smart participants know the answer
to the thread I posted Christmas eve, titled "Method equality; setting
instance variables on Method instances"?

http://groups.google.com/group/comp.lang.ruby/browse_frm/thread/951b2a42731ab66b

I suspect I'm not coming up with anything better than the other methods
discussed here, but it grates on me that I can't figure out what's
going on.

Sorry I didn't answer your original post. I waited until somebody else would answer it, and then I forgot it :frowning:

This has been brought up several times. I'm sure Tom aka trans can tell you a lot about this. Look at the following IRB session:

   irb(main):001:0> Kernel.method( :puts ).object_id
   => 24666490
   irb(main):002:0> Kernel.method( :puts ).object_id
   => 24661050

Module#method always creates a new Ruby object around the internal method implementation. Currently you have to cache your method objects in order to be able to retrieve them later.

Regards,
Pit

ara.t.howard@noaa.gov schrieb:

mine is slowest of all, however it preserves blocks: see if you can speed it up (...)

Ara, maybe your implementation is slow, but at least you are too fast for me :slight_smile: I just wanted to start a new thread asking for alternative techniques...

Your implementation has the syntatical advantage that you can call the previous implementation with the super keyword.

Regards,
Pit

heh - i'm working on making it stack based attm so one can do

   push_method 'foo' do
     'the new foo' or super # super works!
   end

and, later

   pop_method 'foo' # restore super

cheers.

-a

···

On Thu, 28 Dec 2006, Pit Capitain wrote:

ara.t.howard@noaa.gov schrieb:

mine is slowest of all, however it preserves blocks: see if you can speed it up (...)

Ara, maybe your implementation is slow, but at least you are too fast for me :slight_smile: I just wanted to start a new thread asking for alternative techniques...

Your implementation has the syntatical advantage that you can call the previous implementation with the super keyword.

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

Pit Capitain wrote:

Sorry I didn't answer your original post. I waited until somebody else
would answer it, and then I forgot it :frowning:

[snip[

Module#method always creates a new Ruby object around the internal
method implementation. Currently you have to cache your method objects
in order to be able to retrieve them later.

Ah, thanks, that explains it all. (Must have missed the earlier
discussions about this.)

I've ssen a stack used before (but I can't recall where, was it Nitro's
aspect.rb?) Your syntax though is an interesting method-ology :wink:

Hmm....

  defs.push 'foo' do
    ...
  end

  defs.pop

T.

···

ara.t.howard@noaa.gov wrote:

On Thu, 28 Dec 2006, Pit Capitain wrote:

> ara.t.howard@noaa.gov schrieb:
>> mine is slowest of all, however it preserves blocks: see if you can speed
>> it up (...)
>
> Ara, maybe your implementation is slow, but at least you are too fast for me
> :slight_smile: I just wanted to start a new thread asking for alternative techniques...
>
> Your implementation has the syntatical advantage that you can call the
> previous implementation with the super keyword.

heh - i'm working on making it stack based attm so one can do

   push_method 'foo' do
     'the new foo' or super # super works!
   end

and, later

   pop_method 'foo' # restore super

i like that. anyone else?

-a

···

On Thu, 28 Dec 2006, Trans wrote:

Hmm....

defs.push 'foo' do
   ...
end

defs.pop

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

Brain storming:

- Wouldn't it make more sense to say defs.pop 'foo' ?
- If I can say defs.X, then what's defs? Is it an object I can do other stuff with?
- How about injecting a redefinition somewhere other than the top of the stack?

I mean, the basic implementation is plenty cool. Just thought I'd share my crazy ideas....
-Mat

···

On Dec 27, 2006, at 2:08 PM, ara.t.howard@noaa.gov wrote:

On Thu, 28 Dec 2006, Trans wrote:

Hmm....

defs.push 'foo' do
   ...
end

defs.pop

i like that. anyone else?

Hmm....

defs.push 'foo' do
   ...
end

defs.pop

i like that. anyone else?

Brain storming:

- Wouldn't it make more sense to say defs.pop 'foo' ?

yes of course.

- If I can say defs.X, then what's defs? Is it an object I can do other stuff with?

list of objects. probably the list of modules

- How about injecting a redefinition somewhere other than the top of the stack?

possible. but painful. you'd have to do tons of method re-shuffling to
preserver the concept of 'super'. a bit too hard for my tastes, but i'll let
you submit a patch! :wink:

I mean, the basic implementation is plenty cool. Just thought I'd share my
crazy ideas....

always a good thing imho!

cheers.

-a

···

On Thu, 28 Dec 2006, Mat Schaffer wrote:

On Dec 27, 2006, at 2:08 PM, ara.t.howard@noaa.gov wrote:

On Thu, 28 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

Ok, let me hijack this thread back to it's first subject. =P

Considering the example, let's assume we want to redefine the method to encapsulte the old behaviour in a new one. Couldn't this be done in the metaclass[1] and the metaclass calls the object's original definition?

[1] From why's article:
class Object
   def metaclass
     class << self; return self; end;
   end
end

Paulo Köch wrote:

Ok, let me hijack this thread back to it's first subject. =P

Considering the example, let's assume we want to redefine the method
to encapsulte the old behaviour in a new one. Couldn't this be done
in the metaclass[1] and the metaclass calls the object's original
definition?

[1] From why's article:
class Object
   def metaclass
     class << self; return self; end;
   end
end

That's how many of these implementations work, albiet by adding a
module to the singleton class so that more than one layer can be added
as well.

T.