Using an UnboundMethod instead of an alias to redefine a met

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] split-s: Replacing methods
[2]
Jay Fields' Thoughts: Ruby: Alias method alternative

On Wed, 27 Dec 2006, Daniel B. wrote:

 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

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

On Thu, 28 Dec 2006, Pit C. wrote:

def []=(key, value)

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

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 Wed, Dec 27, 2006 at 11:30:05PM +0900, Daniel B. 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)
  • 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

Mauricio F. 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 F. wrote:

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

(* Trans still patiently awaits Cuts *)

T.

[email protected] 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

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

Pit C. 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.)

On Thu, 28 Dec 2006, Pit C. wrote:

[email protected] 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

cheers.

-a

On Thu, 28 Dec 2006, Trans wrote:

Hmm…

defs.push ‘foo’ do

end

defs.pop

i like that. anyone else?

-a

On Dec 27, 2006, at 2:08 PM, [email protected] wrote:

i like that. anyone else?
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

[email protected] wrote:

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

On Thu, 28 Dec 2006, Mat S. wrote:

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

Mauricio F. wrote:

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