[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Method overload, care to improve?

Trans

3/20/2006 11:46:00 PM

Have a need to create method overloading, so I put together an
implementation. Curious what other think of it; how to improve it; or
do it a better way.

Thanks,
T.

# ---- overload.rb

class Module

def method_overloads
@method_overloads ||= {}
end

def overload( name, *signiture, &block )
name = name.to_sym

if method_overloads.key?( name )
method_overloads[name][signiture] = block

else
method_overloads[name] = {}
method_overloads[name][signiture] = block

if method_defined?( name )
#method_overloads[name][nil] = instance_method( name ) #true
alias_method( "#{name}Generic", name )
has_generic = true
else
has_generic = false
end

define_method( name ) do |*args|
ovr = self.class.method_overloads[:"#{name}"]
sig = args.collect{ |a| a.class }
hit = nil
faces = ovr.keys.sort { |a,b| b.size <=> a.size }
faces.each do |cmp|
next unless cmp.size == sig.size
cmp.size.times { |i|
next unless cmp[i] < sig[i]
}
hit = cmp
end

if hit
ovr[hit].call(*args)
else
if has_generic #ovr[nil]
send( "#{name}Generic", *args )
#ovr[nil].bind(self).call(*args)
else
raise NoMethodError
end
end

end

end

end

end


# _____ _
# |_ _|__ ___| |_
# | |/ _ \/ __| __|
# | | __/\__ \ |_
# |_|\___||___/\__|
#

class X

def x
"hello"
end

overload :x, Integer do |i|
i
end

overload :x, String, String do |s1, s2|
[s1, s2]
end

end

x = X.new
p x.x
p x.x(1)
p x.x("a","b")

8 Answers

Andrew Johnson

3/20/2006 11:59:00 PM

0

On 20 Mar 2006 15:46:09 -0800, Trans <transfire@gmail.com> wrote:
> Have a need to create method overloading, so I put together an

IIRC, the 'strongtyping' library on RAA provide method overloading
among other things.

andrew

--
Andrew L. Johnson http://www.s...
What have you done to the cat? It looks half-dead.
-- Schroedinger's wife

Trans

3/21/2006 1:29:00 AM

0

Thanks Andrew.

I'm afraid I don't care for how the strongtyping lib handles
overloading with the #overload call internal to the method. It
undermines dynamic capabilities. Eg.

def bar(*args)
overload(args, String, String) {
| s1, s2 |
...
return
}

overload(args, String, Integer) {
| s, i |
...
return
}

overload_error args
end

It would be tricky to add another overload post defnition of the
original.

T.

Clint

3/21/2006 6:04:00 AM

0

Trans wrote:
> Thanks Andrew.
>
> I'm afraid I don't care for how the strongtyping lib handles
> overloading with the #overload call internal to the method. It
> undermines dynamic capabilities. Eg.
>
> def bar(*args)
> overload(args, String, String) {
> | s1, s2 |
> ...
> return
> }
>
> overload(args, String, Integer) {
> | s, i |
> ...
> return
> }
>
> overload_error args
> end
>
> It would be tricky to add another overload post defnition of the
> original.

I would agree. You loose the most powerful part - to define methods after the
definition. One good example for multimethods are in event callbacks. You can
capture the event you want without a bunch of switch code:

overload :mouse_down, LeftButton, ButtonDown do |button, state, x, y|
puts "left mouse button down"
end

Unfortunately it looks a bit odd. If I was using it in a library internally it
might be cool. But exposing library users to it would be something else. I
wonder if it could be done like this:

class Test
generic :foo

def foo.Integer_String( x, y )
return x * y
end
end

t = Test.new
t.foo( 10, 20 )


Here's a prototype, but doesn't quite work:

def generic( symbol )
class_eval do
#instance_variable_set( "@@#{symbol.to_s}", Object.new )
@@foo = Object.new
define_method( symbol ) do | *args |
# Find applicable method and call
@@foo.Integer_String( *args )
end
end
end


Mike

Clint

3/21/2006 6:45:00 AM

0

Here's a working prototype:

class Test
#generic :foo (would generate the code below)
@@foo = Object.new
define_method :foo do |*args|
method = args.map { |a| a.class }.join( "_" )
@@foo.send( method, *args )
end
end

class Test
def @@foo.Fixnum_Fixnum( x, y )
return x * y
end

def @@foo.Fixnum_Float( x, y )
return x / y
end
end

t = Test.new
t.foo( 10, 20 )
t.foo( 10, 2.0 )

It's inefficient and all that, but I think it looks pretty nice :)
Mike

Trans wrote:
> Thanks Andrew.
>
> I'm afraid I don't care for how the strongtyping lib handles
> overloading with the #overload call internal to the method. It
> undermines dynamic capabilities. Eg.
>
> def bar(*args)
> overload(args, String, String) {
> | s1, s2 |
> ...
> return
> }
>
> overload(args, String, Integer) {
> | s, i |
> ...
> return
> }
>
> overload_error args
> end
>
> It would be tricky to add another overload post defnition of the
> original.

Erik Veenstra

3/21/2006 8:06:00 AM

0

The "methods" are executed in the context of an Object object,
instead of in the context of a Test object... And it behaves
like a singleton... And it doesn't handle subclasses of a
Class....

No, I don't think it's a usable solution...

The code below would probably work better. It uses the
monitor-function "def_overload" which uses Module#wrap_method
[1] to add functionality to an already existing method. If the
arguments of a call matches the fingerprint, the given block
will be executed, otherwise the previous definition of the same
method is checked/called.

gegroet,
Erik V. - http://www.erikve...

[1] http://www.erikve...monitorfunctions/index.html

----------------------------------------------------------------

require "ev/metameta"

class Module
def def_overload(method, *klasses, &block)
wrap_method(method) do |org_method, args|
nok =
args.zip(klasses).find do |a, k|
if k.kind_of?(Class)
not a.kind_of?(k)
else
not a.respond_to?(k)
end
end

if nok
if org_method
org_method.call(*args)
else
raise NotImplementedError, "Method #{self}##{method} not
implemented for arguments #{args.inspect}."
end
else
block.call(*args)
end
end
end
end

class Test
def_overload(:foo, Fixnum, :to_f) do |x, y|
x + y.to_f
end

def_overload(:foo, Fixnum, Fixnum) do |x, y|
x * y
end

def_overload(:foo, Fixnum, Float) do |x, y|
x / y
end
end

t = Test.new

p t.foo(10, 20)
p t.foo(10, 2.0)
p t.foo(10, "2")
p t.foo(10, :two) # Should fail...

----------------------------------------------------------------

Clint

3/21/2006 10:02:00 AM

0

Points taken for the problems of the implementation, and the metameta solution
is nicely done. However, I would like to see if it's possible to use the 'def'
syntax for defining multimethods. Below is a version I have been toying with,
keep in mind the 'self' problem still exists and it is very inefficient. Is it
possible to execute a method in a specific context, or is there a way to make
the following work correctly?

class Generic
def find_method( args )
methods(false).sort.reverse.each do |method_name|
signature = method_name.split( "_" ).map do |i| eval i end
result = args.zip( signature ).map do |m|
m[1] == nil ? true : m[0].kind_of?( m[1] )
end
return method_name if result.inject( true ) { |a, i| a and i }
end
raise NotImplementedError
end
end

class MultimethodTest
@@foo = Generic.new

def @@foo.Fixnum( number )
puts 'foo( Fixnum )'
end

def @@foo.String_Numeric( string, number )
puts 'foo( String, Number )'
end

def foo( *args )
@@foo.send( @@foo.find_method( args ), *args )
end
end

t = MultimethodTest.new
t.foo( 10 )
t.foo( "Hello", 10 )

Erik Veenstra wrote:
> The "methods" are executed in the context of an Object object,
> instead of in the context of a Test object... And it behaves
> like a singleton... And it doesn't handle subclasses of a
> Class....
>
> No, I don't think it's a usable solution...
>
> The code below would probably work better. It uses the
> monitor-function "def_overload" which uses Module#wrap_method
> [1] to add functionality to an already existing method. If the
> arguments of a call matches the fingerprint, the given block
> will be executed, otherwise the previous definition of the same
> method is checked/called.
>
> gegroet,
> Erik V. - http://www.erikve...
>
> [1] http://www.erikve...monitorfunctions/index.html
>
> ----------------------------------------------------------------
>
> require "ev/metameta"
>
> class Module
> def def_overload(method, *klasses, &block)
> wrap_method(method) do |org_method, args|
> nok =
> args.zip(klasses).find do |a, k|
> if k.kind_of?(Class)
> not a.kind_of?(k)
> else
> not a.respond_to?(k)
> end
> end
>
> if nok
> if org_method
> org_method.call(*args)
> else
> raise NotImplementedError, "Method #{self}##{method} not
> implemented for arguments #{args.inspect}."
> end
> else
> block.call(*args)
> end
> end
> end
> end
>
> class Test
> def_overload(:foo, Fixnum, :to_f) do |x, y|
> x + y.to_f
> end
>
> def_overload(:foo, Fixnum, Fixnum) do |x, y|
> x * y
> end
>
> def_overload(:foo, Fixnum, Float) do |x, y|
> x / y
> end
> end
>
> t = Test.new
>
> p t.foo(10, 20)
> p t.foo(10, 2.0)
> p t.foo(10, "2")
> p t.foo(10, :two) # Should fail...
>
> ----------------------------------------------------------------
>

Erik Veenstra

3/21/2006 10:35:00 AM

0

> Is it possible to execute a method in a specific context,

As long as the objects are of the same class, you could use
Method#unbind and UnboundMethod#bind. If they aren't, you could
experiment with Method#to_proc.

> @@foo = Generic.new

Why?...

gegroet,
Erik V. - http://www.erikve...

----------------------------------------------------------------

class Foo
def bar
self.object_id
end
end

foo1 = Foo.new
foo2 = Foo.new

p foo1.bar
p foo2.bar

p foo1.method(:bar).unbind.bind(foo2).call
p Foo.instance_method(:bar).bind(foo2).call

----------------------------------------------------------------

Clint

3/21/2006 11:37:00 AM

0

Erik Veenstra wrote:
>> Is it possible to execute a method in a specific context,
>
> As long as the objects are of the same class, you could use
> Method#unbind and UnboundMethod#bind. If they aren't, you could
> experiment with Method#to_proc.

Thanks, I'll give it a try.

>> @@foo = Generic.new
>
> Why?...

I was trying to keep the class namespace clean and to consolidate the methods
into their own space. The unbind/bind technique is interesting - now I tried
making the generic object the same class, and doing this:

def foo( *args )
method = @@foo.method( @@foo.find_method( args ) )
method.unbind.bind( self ).call( *args )
end

But alas, I get:
generic.rb:33:in `bind': singleton method called for a different object (TypeError)

I guess I don't fully understand how singleton methods differ from normal
methods. Thanks for all your time, this is just a curiosity not for a project
per se.

> gegroet,
> Erik V. - http://www.erikve...
>
> ----------------------------------------------------------------
>
> class Foo
> def bar
> self.object_id
> end
> end
>
> foo1 = Foo.new
> foo2 = Foo.new
>
> p foo1.bar
> p foo2.bar
>
> p foo1.method(:bar).unbind.bind(foo2).call
> p Foo.instance_method(:bar).bind(foo2).call
>
> ----------------------------------------------------------------