From: Mark Hubbart Date: 2004-04-20T07:33:07+09:00 Subject: proposal: call_up() for use in redefined methods Hi all, A common ruby idiom for redefining a method in a class is to alias it to give it a new name before defining the replacement: # allow integer indices to wrap. # "test"[6] #=>"e" class String alias :old_brackets :[] def [](idx, num=nil) idx = ((idx % size) + idx) % size if idx.kind_of? Fixnum num ? old_brackets(idx, num) : old_brackets(idx) end end However, this breaks if it is done a second time, with the same alias: # allow ranges to wrap around # "abcd"[-2..1] #=>"cdab" class String alias :old_brackets :[] def [](idx, num=nil) if Range === idx and idx.first < 0 and idx.end > 0 end_range = idx.exclude_end?? (0...idx.end) : (0..idx.end) self[idx.first..-1] + self[end_range] else num ? old_brackets(idx, num) : old_brackets(idx) end end end then in irb: irb(main):004:0>>> "abcd"[3] (eval):4:in `old_brackets': stack level too deep (SystemStackError) from (eval):5:in `old_brackets' from (eval):5:in `old_brackets' This can be a problem. If those two definitions occurred in two separate libraries, then the maintainers of both libraries would have to get together (once the problem is identified) and modify their code just so the libraries can be used together. It could discourage people from using what is (IMHO) one of Ruby's greatest strengths... the ability to easily redefine methods. One solution comes from the common OO method/construct: super(). Calling super() lets you call the parent class' version of a particular function; it's smart, and knows to call the same function that you are currently in. Since most OO languages (that I've worked with) have non-redefinable classes, super() is enough. But when you redefine a method in Ruby, there's no way of getting to the previously defined method. I propose a new method/construct, tentatively named call_up(), which would give access to the older versions of a method; and a companion method redefine() which stores the method in a stack before undefing it. Here's a partial example of a hacked solution. Additional coding would be needed to support the same thing in modules and singleton classes, I but it shows a rough idea of the way it would work. class Object def redefine(sym) if implements? sym @@__redefined_methods[sym] ||= [] @@__redefined_methods[sym].push method(sym) undef sym else raise ArgumentError, "not a valid method" end end def call_up(*args) # Deep Magic Goes Here: # - detects the calling method # - detects if it is in the __redefined_methods stack # - determines which method version was defined immediately previous # - calls that method with *args end end Each class (and singleton object) would keep track of the explicitly redefined methods, and allow a super()ish way of calling the next method up. This could allow the above method redefinition code to look like this: # allow integer indices to wrap. # "test"[6] #=>"e" class String redefine :[] def [](idx, num=nil) idx = ((idx % size) + idx) % size if idx.kind_of? Fixnum num ? call_up(idx, num) : call_up(idx) end end # allow ranges to wrap around # "abcd"[-2..1] #=>"cdab" class String redefine :[] def [](idx, num=nil) if Range === idx and idx.first < 0 and idx.end >= 0 end_range = idx.exclude_end?? (0...idx.end) : (0..idx.end) self[idx.first..-1] + self[end_range] else num ? call_up(idx, num) : call_up(idx) end end end If we could do this, it would eliminate any worries about conflicting method names, and would free Ruby programmers to redefine methods whenever it makes sense. I don't think it would introduce any real added complexity, and it would standardize how to redefine a method and call the original. For an even more real-world example, consider all the different numeric types defined in the stdlib math libraries. If you want to create a new Numeric, and want Math.sqrt() to support it, you have to redefine Math.sqrt. But if it just happens that someone else did the same thing, in the same way, then everything will go horribly wrong the moment you use Math.sqrt() after require()ing both files. Comments? Questions? Flames? :) --Mark