proposal: call_up() for use in redefined methods

M

Mark Hubbart

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 :eek:ld_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 :eek:ld_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'
<snip long stack error>

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
 
H

Hal Fulton

Mark Hubbart wrote:

[snip]
Comments? Questions? Flames? :)

I think this is essentially the same as my suggestion in
http://ruby-talk.org/38136

The answer then was basically: Something like this will come
later.

In fact, I believe that Matz's plans for pre/post/wrap would make
this sort of thing easy (and far more generalized).

Unless I've misunderstood.


Cheers,
Hal
 
M

Mark Hubbart

Mark Hubbart wrote:

[snip]
Comments? Questions? Flames? :)

I think this is essentially the same as my suggestion in
http://ruby-talk.org/38136

Damn! you're right - and I like "prior" better, too :) I should've
googled it a little better, might have turned that up.
The answer then was basically: Something like this will come
later.

In fact, I believe that Matz's plans for pre/post/wrap would make
this sort of thing easy (and far more generalized).

hmmm... from what I've read, it appears the hooks are just methods, and
they can be redefined. So if you re-open a class and define foo:wrap,
and foo was already wrapped, it looks like the new wrapper will replace
the old one, rather than wrapping the old wrapper.

The beauty of the "prior" method is that you can add support for other
object types easily, by redefining the method. So if you are writing
libraries for HyperNumerics and PsuedoNumerics, you just go:

# file hypernumeric.rb
module Math
redefine :sqrt
def sqrt(obj)
if obj.kind_of? HyperNumeric
# handle hypernumeric numbers
else prior(obj)
end
end
end

# file psuedonumeric.rb
module Math
redefine :sqrt
def sqrt(obj)
if obj.kind_of? PsuedoNumeric
# handle psuedonumeric numbers
else prior(obj)
end
end
end

no method entanglement, no namespace clutter... And it doesn't matter
which one gets defined first, since it just grabs the argument if it
can handle it; otherwise it passes it on.
Unless I've misunderstood.

Or me :) I might easily have misunderstood how the pre/post/wrap thing
will work... I'm sorta unsure of it, the more I think about it...

--Mark
 
J

Jean-Hugues ROBERT

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

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 :eek:ld_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 :eek:ld_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'
<snip long stack error>

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
 
G

gabriele renzi

il Tue, 20 Apr 2004 17:03:30 +0900, Jean-Hugues ROBERT
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,

and is super() in rubyland (2.0)
 
M

Mauricio Fernández

Mark Hubbart wrote:

[snip]
Comments? Questions? Flames? :)

I think this is essentially the same as my suggestion in
http://ruby-talk.org/38136

It seems nobody saw this posting of mine:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/97440

That code allows you to do

class A
def foo(*a)
puts "A#foo #{a.inspect}"
end
end
a = A.new
a.foo

class A
def_advice:)foo) do |prior, *a|
puts "before"
prior[*a]
puts "after"
end
end
a.foo

i.e. the previous definition is available as prior (or whatever name
you care to give it :). The most important drawback is that it is
still impossible to propagate blocks that way (until matz implements
{|&block|...}).
The answer then was basically: Something like this will come
later.

In fact, I believe that Matz's plans for pre/post/wrap would make
this sort of thing easy (and far more generalized).

matz' advices will be more powerful, see the references in [97440]
for more info.

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

'Ooohh.. "FreeBSD is faster over loopback, when compared to Linux
over the wire". Film at 11.'
-- Linus Torvalds
 
D

David Alan Black

Hi --

gabriele renzi said:
il Tue, 20 Apr 2004 17:03:30 +0900, Jean-Hugues ROBERT


and is super() in rubyland (2.0)

That would be strange. Are you sure about that?


David
 
G

gabriele renzi

il 20 Apr 2004 15:13:04 -0700, David Alan Black <[email protected]>
ha scritto::

That would be strange. Are you sure about that?

maybe I misunderstood something?

the thing the OP requested looks to me the method
decoration/defadvice-ish thing that we'll see in ruby2, and from the
rubyconf 2k3 it seem that the syntax would be:

def foo:wrap
blabla
super
blablabla
end

that makes sense cause a redefinition in a subclass may be seen just
like a decoration of the first definition.

Next in perl6 should work somehow like this in that it allows method
combination calling a method from another, and even if probably it
does more things (my head was spinning at that point of apocalypse12)
it gives, imo, similar functionality.
 
D

David Alan Black

Hi --

gabriele renzi said:
il 20 Apr 2004 15:13:04 -0700, David Alan Black <[email protected]>
ha scritto::



maybe I misunderstood something?

the thing the OP requested looks to me the method
decoration/defadvice-ish thing that we'll see in ruby2, and from the
rubyconf 2k3 it seem that the syntax would be:

def foo:wrap
blabla
super
blablabla
end

that makes sense cause a redefinition in a subclass may be seen just
like a decoration of the first definition.

Yes, I forgot that 'super' was being used for that. But I'm not sure
this corresponds exactly to what the OP wanted (a way to access
aliased methods, which is what I thought you meant here). But maybe
it can be used in some of the same situations.

For what it's worth, I don't agree that it's very similar to
redefinition in a subclass. I would actually find it hard to find a
unified way to explain both 'super's to someone, and I'm not sure why
the same word is being reused. I guess 'wrapee' is a bit too bizarre
:) But I do wish it were something other than super, since the
wrapped method is not 'above' the wrapper (the way a superclass's
method is above the subclass's method).


David
 
N

Nathaniel Talbott

For what it's worth, I don't agree that it's very similar to
redefinition in a subclass. I would actually find it hard to find a
unified way to explain both 'super's to someone, and I'm not sure why
the same word is being reused. I guess 'wrapee' is a bit too bizarre
:) But I do wish it were something other than super, since the
wrapped method is not 'above' the wrapper (the way a superclass's
method is above the subclass's method).

The wrapper is really 'around' the method... so I like 'inner' myself.


Nathaniel
Terralien, Inc.

<:((><
 
L

Lothar Scholz

Hello Curt,

Wednesday, April 21, 2004, 12:41:52 AM, you wrote:

I get the following error from the splitter sample

Z:\work\arachno\make>"C:\Program Files\Arachno Ruby IDE\ruby\samples\wxRuby-0.3.0\splitter\splitter.
rb"
C:/Program Files/Arachno Ruby IDE/ruby/samples/wxRuby-0.3.0/splitter/splitter.rb:257:in `main_loop':
undefined method `on_draw' for #<MyCanvas:0x2a54830> (NoMethodError)
from C:/Program Files/Arachno Ruby IDE/ruby/samples/wxRuby-0.3.0/splitter/splitter.rb:257

All other samples seem to work fine.
 
M

Mark Hubbart

the thing the OP requested looks to me the method
decoration/defadvice-ish thing that we'll see in ruby2, and from the
rubyconf 2k3 it seem that the syntax would be:

def foo:wrap
blabla
super
blablabla
end

that makes sense cause a redefinition in a subclass may be seen just
like a decoration of the first definition.

Okay, maybe someone could clear this up... how exactly will the
defadvice stuff work? once you wrap a method, is the wrapper permanent?
For example, can you:

class Foo
def foo
print "foo"
end
def foo:wrap
print ":"; super; print ":"
end
end

then, later reopen the class and:

class Foo
def foo:wrap
print "."; super; print "."
end
end

...and expect that calling Foo.new.foo will print ".:foo:."? If so,
that would solve the problem. If not, it would (obviously) allow only
one redefinition per method.

I see the ability to call "prior" (or whatever it would be called) as a
solution to the multiple redefinition problem... I first thought of it
while browsing through the math libraries, and seeing what was done to
get new Numerics to work with other libraries. They all had to be
careful not to step on each others feet.

cheers,
--Mark
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: proposal: call_up() for use in redefined methods"

|Okay, maybe someone could clear this up... how exactly will the
|defadvice stuff work? once you wrap a method, is the wrapper permanent?

You can stack wrapper methods in a class, so that

|For example, can you:
|
| class Foo
| def foo
| print "foo"
| end
| def foo:wrap
| print ":"; super; print ":"
| end
| end
|
|then, later reopen the class and:
|
| class Foo
| def foo:wrap
| print "."; super; print "."
| end
| end
|
|...and expect that calling Foo.new.foo will print ".:foo:."

should work as you expected. I haven't designed the API to remove the
around (and before/after) methods yet.

matz.
 
M

Mark Hubbart

Hi,

In message "Re: proposal: call_up() for use in redefined methods"

|Okay, maybe someone could clear this up... how exactly will the
|defadvice stuff work? once you wrap a method, is the wrapper
permanent?

You can stack wrapper methods in a class, so that [snip my code]
should work as you expected. I haven't designed the API to remove the
around (and before/after) methods yet.

matz.

So you can either add a second wrapper, or replace the older one! Very
cool :)

Thanks,
--Mark
 
J

Jim Freeze

wxRuby 0.3.0 has been released and is now available for download from
RubyForge at:

http://wxruby.rubyforge.org/

This release includes binary builds for Max OS X Panther and MS Windows.
Hopefully, within a couple weeks I will release RubyGems for the source, and
RubyGem binaries for Mac, Windows, and Linux.

Hmm, I installed the Mac OS X version, but I can't seem to find where it
put it. It's not in my local install of ruby, nor in /sw nor in
/usr/local.
 

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

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,817
Latest member
DicWeils

Latest Threads

Top