From: Ken Bloom Date: 2006-09-09T01:30:18+09:00 Subject: Re: [CHALLENGE] better alias_method On Fri, 08 Sep 2006 23:09:07 +0900, Logan Capaldo wrote: > On Sep 8, 2006, at 1:34 AM, ara.t.howard@noaa.gov wrote: > >> >> i've been wanting a better alias_method for quite some time. >> essentially i'd >> like a way out of the trap where executing >> >> alias_method '__fubar__', 'fubar' >> >> goes haywire when __fubar__ already exists. we've all seen it >> happen before. >> >> anyhow. the interface it'd like would be >> >> >> class C >> def m() 'a' end >> >> push_method 'm' >> >> def m() super + 'b' end >> end >> >> p C.new.m #=> 'ab' >> >> >> what i've got is quite close, but no cigar. it has a fundemental >> problem with >> the way ruby scopes super which i'm too tired atttm to figure out. >> i'm hoping >> i can go to bed and wake up to a nice patch ;-) here's what i've got: >> >> >> harp:~ > cat a.rb >> class Module >> def push_method m >> this = self >> >> include Module.new{ >> @m = this.instance_method m >> >> this.module_eval{ remove_method m } >> >> module_eval <<-code >> def #{ m }(*a, &b) >> um = ObjectSpace._id2ref #{ @m.object_id } >> um.bind(self).call *a, &b >> end >> code >> } >> end >> end >> >> class C >> def m >> 'a' >> end >> p new.m #=> 'a' >> >> >> push_method 'm' >> >> >> def m >> super + 'b' >> end >> p new.m #=> 'ab' >> >> >> push_method 'm' >> >> >> def m >> super + 'c' >> end >> p new.m #=> 'abc' >> end >> >> >> >> harp :~ > ruby a.rb >> "a" >> "ab" >> a.rb:31:in `m': stack level too deep (SystemStackError) >> from (eval):3:in `m' >> from a.rb:31:in `m' >> from (eval):3:in `m' >> from a.rb:31:in `m' >> from (eval):3:in `m' >> from a.rb:31:in `m' >> from (eval):3:in `m' >> from a.rb:31:in `m' >> ... 2343 levels... >> from a.rb:31:in `m' >> from (eval):3:in `m' >> from a.rb:40:in `m' >> from a.rb:42 >> >> >> have at it - i'll be back in 8 hrs. ;-) >> >> > > I have this, it takes a different approach though: > [Code snipped. You'll have to look above if you want to know how the broken code works] > > Darn. You seem to have made me discover a bug in my impl. It doesn't > work for more than one level of patching per method. Well maybe someone > will give me a patch too ;) caller knows a function only by the name you call it. hence class A def hyper p caller end def a hyper end alias_method :b,:a end a=A.new puts "Calling by a" a.a puts "Calling by b" a.b gives: Calling by a ["(irb):7:in `a'", "(irb):14:in `irb_binding'", "/usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", ":0"] Calling by b ["(irb):7:in `b'", "(irb):16:in `irb_binding'", "/usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", ":0"] In your code, we need to do several things: * rename and call them by their new names to prevent infinite recursion * keep track of pre_patched_versions correctly when their name changes So here's my fix for your code, to incorporate these ideas. I think that if anyone uses straight up alias_method on methods that we are patching, things will break, so it's probably a good idea to figure out how to fix that too. module Patchable module ClassMethods def unused_alias newalias=nil while newalias==nil or instance_methods.include?(newalias) newalias=:"__kenoverride__#{rand(10**20)}__" end newalias end def patch(method_name = nil, &new_body) if method_name method_name = method_name.to_sym w=unused_alias alias_method w,method_name remove_method method_name pre_patched_versions[w]=pre_patched_versions[method_name.to_sym]\ if pre_patched_versions.include?(method_name.to_sym) pre_patched_versions[method_name.to_sym] = w define_method(method_name, &new_body) else klass = Class.new imeths = klass.instance_methods klass.class_eval(&new_body) new_meths = klass.instance_methods - imeths new_meths.each do |m| w=unused_alias alias_method w,m remove_method m pre_patched_versions[w]=pre_patched_versions[m.to_sym]\ if pre_patched_versions.include?(m.to_sym) pre_patched_versions[m.to_sym] = w end class_eval(&new_body) end self end def pre_patched_versions @pre_patched_versions ||= {} end end def hyper(*args, &block) meth_name = caller[0][/`([^']+)'/, 1].to_sym tocall=self.class.pre_patched_versions[meth_name] send(tocall,*args, &block) end def self.included(other) other.extend(ClassMethods) end end class C include Patchable def m 'a' end p new.m patch do def m raise StandardError if caller.length>10 hyper + 'b' end end p new.m patch do def m raise StandardError if caller.length>10 hyper + 'c' end end p new.m #you didn't have a test case for this, but I added it patch :m do hyper+'d' end p new.m end -- Ken Bloom. PhD candidate. Linguistic Cognition Laboratory. Department of Computer Science. Illinois Institute of Technology. http://www.iit.edu/~kbloom1/