From: Jean-Hugues ROBERT Date: 2004-04-20T17:03:30+09:00 Subject: Re: proposal: call_up() for use in redefined methods Hi, FYI, Perl 6's Apocalypse 12 describes a mechanism that seems similar to yours. You may want to have a look at it. I think call_up() is "next" or something. Yours, Jean-Hugues Robert At 07:33 20/04/2004 +0900, you wrote: >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 ------------------------------------------------------------------------- Web: http://hdl.handle.net/1030.37/1.1 Phone: +33 (0) 4 92 27 74 17