11

I want to use overloading feature in Ruby like many other languages, but Ruby itself does not support this feature.

Do I have to implement it using the way that define a method with *args argument and determine the number and types of the arguments inside the method? Some like:

class A
    def foo(*args)
        case (args.length)
        when 1
            do something
        when 2
            do something-else
        ....
        end
    end
end

You can see, it is really ugly than directly overloading.

I want to know whether there is any keywords or some other manners (like a meta-programming module) that could allow me to define an overloading method in a more elegant way.

shouya
  • 2,863
  • 1
  • 24
  • 45
  • 1
    I don't like the length option... as args can be passed as nil.. at the very least this is hard to read. – Arth Apr 14 '12 at 20:24
  • 3
    You should try to adapt to the Ruby way. There is a reason why there is no overloading in Ruby. Methods should only do one thing, not magically decide to do vastly different things just because of different arguments. Instead try to take advantage of [Duck Typing](http://en.wikipedia.org/wiki/Duck_typing) and if in doubt, use different methods with meaningful names. – Holger Just Apr 14 '12 at 21:00
  • A further inquiry: Overloading only with number of parameters (as in your example) or also with type of parameter? Or in an example: Are the method calls `a(1)` and `a('x')` the same call or do the call different methods? – knut Apr 14 '12 at 21:05
  • Ruby has multi parameters but i personally never use it. If i wanna pass an unknown number of parameters , i would make it an array . For example, `def foo(option=[]) option.each{} end` – SwiftMango Apr 14 '12 at 21:59
  • @HolgerJust Thank you that I think I learned something really important.For this, I choose to use another method to do the different operation. Well, I found it is not absolutely needing overload or such stuff like so, really. – shouya Apr 14 '12 at 22:28
  • @knut I mean, better with type than without, but originally it is not required strictly. As dynamic language like Python does not have type-overload as well, not as C++ or such static ones. – shouya Apr 14 '12 at 22:37
  • To be complete: See also http://stackoverflow.com/questions/3958735/in-ruby-is-there-a-way-to-overload-the-initialize-constructor and http://stackoverflow.com/questions/9373104/why-does-ruby-not-support-method-overloading (the 2nd question is more interesting) – knut Apr 14 '12 at 22:44

4 Answers4

5

You can test for the existence of each argument separately as they are set to nil if not passed (assuming they are passed in order!).

If you insist on very different arguments I suggest an hash argument with symbols for each argument you intend.. and approriate tests.

** UPDATE **

Also you could also rename the methods that overload with more specific names, such as

def perform_task_with_qualifier_1
Arth
  • 12,789
  • 5
  • 37
  • 69
5

You could try some meta programming to reach your target.

See the following code:

class OverloadError < ArgumentError; end
class Class
=begin rdoc

=end
  def define_overload_method( methodname, *methods )    
    methods.each{ | proc |
      define_method("#{methodname}_#{proc.arity}".to_sym, &proc )
    }
    define_method(methodname){|*x|
      if respond_to?("#{methodname}_#{x.size}")
      send "#{methodname}_#{x.size}", *x
      else
        raise OverloadError, "#{methodname} not defined for #{x.size} parameters"
      end
    }

  end
end

class X
  define_overload_method :ometh,
        Proc.new{ "Called me with no parameter" },
        Proc.new{ |p1| "Called me with one parameter (#{p1.inspect})" },
        Proc.new{ |p1,p2| "Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end

x = X.new

p '----------'
p x.ometh()
p x.ometh(1)
p x.ometh(1,2)
p x.ometh(1,2,3)  #OverloadError

You can define your overloaded method with define_overload_method. Parameters are the method name and a list of procedures. The method methodname is created and calls the corresponding method. Which method is determined by the number of parameters (Not type!).

An alternative syntax would be:

class OverloadError < ArgumentError; end
class Class
  def def_overload( methodname)    
    define_method(methodname){|*x|
      if respond_to?("#{methodname}_#{x.size}")
      send "#{methodname}_#{x.size}", *x
      else
        raise OverloadError, "#{methodname} not defined for #{x.size} parameters"
      end
    }
  end
  def overload_method( methodname, proc )    
    define_method("#{methodname}_#{proc.arity}".to_sym, &proc )
  end
end
class X
  def_overload :ometh
  overload_method :ometh, Proc.new{ "Called me with no parameter" }
  overload_method :ometh, Proc.new{ |p1| "Called me with one parameter (#{p1.inspect})" }
  overload_method :ometh, Proc.new{ |p1,p2| "Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end

def_overload defines the frame for your overloaded methods, overload_method defines one 'overload-method'.

But as already mentioned by Holger:

You should try to adapt to the Ruby way. There is a reason why there is no overloading in Ruby. Methods should only do one thing, not magically decide to do vastly different things just because of different arguments. Instead try to take advantage of Duck Typing and if in doubt, use different methods with meaningful names.


I was curious how I could implement a version with type sensitive overloading. Here it is:

class OverloadError < ArgumentError; end
class Class
  def def_overload( methodname)    
    define_method(methodname){|*x|
      methname = "xxx"
      methname = "#{methodname}_#{x.size}#{x.map{|p| p.class.to_s}.join('_')}"
      if respond_to?(methname)
        send methname, *x
      elsif respond_to?("#{methodname}_#{x.size}")
        send "#{methodname}_#{x.size}", *x
      else
        raise OverloadError, "#{methodname} not defined for #{x.size} parameters"
      end
    }
  end
  def overload_method( methodname, *args, &proc )    
    types = []
    args.each{|arg| types << arg.to_s}
    define_method("#{methodname}_#{proc.arity}#{types.join('_')}".to_sym, &proc )
  end
end
class X
  def_overload :ometh
  overload_method(:ometh){ "Called me with no parameter" }
  overload_method(:ometh, String ){ |p1| "Called me with one string parameter (#{p1.inspect})" }
  overload_method(:ometh ){ |p1| "Called me with one parameter (#{p1.inspect})" }
  overload_method(:ometh){ |p1,p2| "Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end

When you call it with

p x.ometh(1)
p x.ometh('a')

You get

    "Called me with one parameter (1)"
    "Called me with one string parameter (\"a\")"
Community
  • 1
  • 1
knut
  • 27,320
  • 6
  • 84
  • 112
  • Your method is such a beautiful implement and it could satisfy my requirement completely. However, for Holger's advice, I have decided to change my way. Thank you, anyway. :) – shouya Apr 14 '12 at 22:32
  • I think that's the better decision ;) In meantime I found a type sensitve overloading solution. I extended my answer. – knut Apr 14 '12 at 22:39
  • I thought to build a gem out of my code. It seems somebody else had already the same idea: http://rubygems.org/gems/overload The solution uses a similar logic. – knut Apr 15 '12 at 09:43
  • I think you could consider about using the ancestors chain to process type sensitive overloads. That could be more general. – shouya Apr 15 '12 at 11:43
  • what i have to do when i am overloaded with method(n) and method(q=[],n) ? –  Sep 03 '14 at 04:50
1

there are few gems that provide this feature to your ruby code

  1. functional-ruby
       defn(:greet, :male) {
         puts "Hello, sir!"
       }

       defn(:greet, :female) {
         puts "Hello, ma'am!"
       }

       foo.greet(:male)   => "Hello, sir!"
       foo.greet(:female) => "Hello, ma'am!"

you can find more Elixir like pattern matching features from here

  1. contracts.ruby
       Contract 1 => 1
       def fact x
         x
       end

       Contract C::Num => C::Num
       def fact x
        x * fact(x - 1)
       end

this gem helps to right beautiful defensive code. there are some criticisms about performance. so benchmark and decide. more examples

Oshan Wisumperuma
  • 1,808
  • 1
  • 18
  • 32
-1

The defining characteristic of overloading is that dispatch happens statically. In Ruby, dispatch always happens dynamically, there is no other way. Therefore, overloading is not possible in Ruby.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653