[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Using an UnboundMethod instead of an alias to redefine a method

Daniel Berger

12/27/2006 2:28:00 PM

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-me...
[2]
http://jayfields.blogspot.com/2006/12/ruby-alias-method-altern...

17 Answers

Ara.T.Howard

12/27/2006 2:57:00 PM

0

Pit Capitain

12/27/2006 3:44:00 PM

0

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

Ara.T.Howard

12/27/2006 4:12:00 PM

0

Mauricio Fernández

12/27/2006 4:25:00 PM

0

On Wed, Dec 27, 2006 at 11:30:05PM +0900, Daniel Berger 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/CapturingMe...
--
Mauricio Fernandez - http://eige... - singular Ruby

Gavin Kistner

12/27/2006 4:37:00 PM

0

Mauricio Fernandez wrote:
> [1] some other examples
> http://thekode.net/ruby/techniques/CapturingMe...

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/951b2a...

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.

Trans

12/27/2006 4:40:00 PM

0


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

(* Trans still patiently awaits Cuts *)

T.


Pit Capitain

12/27/2006 4:52:00 PM

0

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/951b2a...
>
> 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

Pit Capitain

12/27/2006 5:00:00 PM

0

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

Ara.T.Howard

12/27/2006 5:07:00 PM

0

Gavin Kistner

12/27/2006 6:02:00 PM

0

Pit Capitain wrote:
> 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.)