30 Days of Tech: Day 6 - Overloading
June 6th, 2008
One thing new Ruby users are sometimes surprised by is Ruby’s lack of overloaded methods. Once you get used to duck typing you quickly realize why this is a strength for Ruby rather than a weakness. Nonetheless I have often wondered what it would look like to implement something resembling method overloading in Ruby. To find out, I put together the hack below allowing overloaded methods to be defined on a class. It allows for adding of new overloaded definitions in a single class, but currently doesn’t have very good behavior when dealing with subclasses or modules. To reiterate, I don’t think this is a good idea, something Ruby should support, or that you should actually use the code below. I hope you find it interesting though.
the spec:
require 'method_overloading'
describe "Method overloading" do
it "should allow methods to be invoked" do
klass = Class.new do
overloading :my_method do
_(Fixnum) {|n| n*2}
end
end
klass.new.my_method(3).should == 6
end
it "should maintain receiver as self within method block" do
klass = Class.new do
overloading :my_method do
_(Object) {|n| self}
end
end
instance = klass.new
instance.my_method(3).should equal(instance)
end
it "should raise error if no matching implementation can be found" do
klass = Class.new do
overloading :my_method do
_(Fixnum) {|n| n}
end
end
lambda {
klass.new.my_method("String")
}.should raise_error(NoMethodError, "No implementation found for my_method(String)")
end
it "should invoke different implementations based on argument type" do
klass = Class.new do
overloading :my_method do
_(Fixnum) {|n| n*2}
_(String) {|s| s.upcase}
end
end
klass.new.my_method(3).should == 6
klass.new.my_method("A String").should == "A STRING"
end
it "should invoke superclass implementations for arguments in type of subclass" do
klass = Class.new do
overloading :my_method do
_(Numeric) {|n| n * 2}
end
end
klass.new.my_method(3).should == 6
klass.new.my_method(3.0).should == 6.0
end
it "should invoke implementation that most closely matches argument type" do
klass = Class.new do
overloading :my_method do
_(Numeric) {|n| n * 2}
_(Integer) {|n| n * 3}
_(Object) {|n| n * 4}
end
end
klass.new.my_method(3).should == 9
end
it "should allow reopening to change a single overloaded method" do
klass = Class.new do
overloading :my_method do
_(String) {|n| "original string implementation"}
_(Fixnum) {|n| "original fixnum implementation"}
end
end
klass.class_eval do
overloading :my_method do
_(String) {|n| "new string implementation"}
end
end
klass.new.my_method("a").should == "new string implementation"
klass.new.my_method(1).should == "original fixnum implementation"
end
it "should allow multiple methods to be overloaded with separate implementations" do
klass = Class.new do
overloading :method_1 do
_(String) {|n| "method_1 string implementation"}
end
overloading :method_2 do
_(String) {|n| "method_2 string implementation"}
end
end
klass.new.method_1("a").should == "method_1 string implementation"
klass.new.method_2("a").should == "method_2 string implementation"
end
end
the implementation:
class Class
def overloading_definition_context(method_name)
@definition_contexts ||= {}
@definition_contexts[method_name] ||= OverloadingDefinitionContext.new(self, method_name)
end
def overloading(method_name, &block)
overloading_definition_context(method_name).instance_eval &block
define_method(method_name) do |arg|
send(self.class.overloading_definition_context(method_name)[arg.class],
arg)
end
end
class OverloadingDefinitionContext
attr_accessor :implementation_block
def initialize(klass, method_name)
@klass = klass
@method_name = method_name
@implementations = {}
end
def _(arg_type, &block)
method_name = "__#{@method_name}_#{arg_type.object_id.abs}"
@klass.send(:define_method, method_name, &block)
@implementations[arg_type] = method_name
end
def [](target_arg_type)
@implementations[implementation_arg_type(target_arg_type)]
end
def implementation_arg_type(target_arg_type)
possible_arg_types = @implementations.keys.select do |arg_type|
target_arg_type <= arg_type
end
if possible_arg_types.empty?
raise NoMethodError.new("No implementation found for #{@method_name}(#{target_arg_type})")
end
possible_arg_types.sort.first
end
end
end
Sorry, comments are closed for this article.