Using an UnboundMethod instead of an alias to redefine a method

D

Daniel Berger

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 :eek:ld_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
 
A

ara.t.howard

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...st&q=harp+push_method&rnum=1#09a22a5ca639834f

regards.

-a
 
P

Pit Capitain

Daniel said:
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 :eek:ld_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
 
A

ara.t.howard

Daniel said:
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 :eek:ld_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)

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


harp:~ > cat a.rb
class HashUsingAlias < Hash
alias :eek:ld_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
 
M

Mauricio Fernandez

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
 
P

Phrogz

Mauricio said:

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

Trans

Mauricio said:
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.
 
P

Pit Capitain

Phrogz said:
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 :-(

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
 
P

Pit Capitain

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 :) 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
 
A

ara.t.howard

Ara, maybe your implementation is slow, but at least you are too fast for me
:) 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
 
P

Phrogz

Pit said:
Sorry I didn't answer your original post. I waited until somebody else
would answer it, and then I forgot it :-( [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.)
 
T

Trans

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

Hmm....

defs.push 'foo' do
...
end

defs.pop

T.
 
M

Mat Schaffer

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
 
A

ara.t.howard

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! ;-)
I mean, the basic implementation is plenty cool. Just thought I'd share my
crazy ideas....

always a good thing imho!

cheers.

-a
 
P

Paulo Köch

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
 
D

Daniel Berger

Mauricio said:
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. :)

Regards,

Dan
 
T

Trans

Paulo said:
Ok, let me hijack this thread back to it's first subject. =3DP

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=2E
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top