Forum: Ruby Method overload, care to improve?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Trans (Guest)
on 2006-03-21 01:48
(Received via mailing list)
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")
Andrew J. (Guest)
on 2006-03-21 02:04
(Received via mailing list)
On 20 Mar 2006 15:46:09 -0800, Trans <removed_email_address@domain.invalid> 
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
Trans (Guest)
on 2006-03-21 03:33
(Received via mailing list)
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.
Mike A. (Guest)
on 2006-03-21 08:09
(Received via mailing list)
Trans wrote:
>             return
>
> 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
Mike A. (Guest)
on 2006-03-21 08:49
(Received via mailing list)
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
Erik V. (Guest)
on 2006-03-21 10:09
(Received via mailing list)
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...
Mike A. (Guest)
on 2006-03-21 12:03
(Received via mailing list)
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 V. (Guest)
on 2006-03-21 12:39
(Received via mailing list)
> 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
Mike A. (Guest)
on 2006-03-21 13:38
(Received via mailing list)
Erik V. 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.
This topic is locked and can not be replied to.