Method overload, care to improve?

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")

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

andrew

···

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

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

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.

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

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 :slight_smile:
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.

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.erikveen.dds.nl/

[1] http://www.erikveen.dds.nl/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...

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

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.erikveen.dds.nl/

[1] Ruby Monitor-Functions - Or Meta-Meta-Programming in Ruby

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

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...

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

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.erikveen.dds.nl/

···

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

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

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

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.erikveen.dds.nl/

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

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

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