Module#method_added
Posted by Jeremy Voorhis Sun, 08 Apr 2007 22:16:00 GMT
Two days ago, I discovered Module#method_added lurking about in the DRP library. Today, let’s take a look at what it does, along with an example implementation of multiple dispatch for Ruby objects.
Module#method_added accepts one parameter – a symbol allowing us to reference the method that was just defined in a class or module definition. We may do a number of things with only that method name – inspect the method, rename it, change its scope, or – in our case – register it with our multiple dispatch system.
The following code is both crude and simple: it allows us to define multimethods based on their arity, and does not support optional arguments or variable-length argument lists. It also only takes hold within the multi do ... end block. With a little extra work, the multi block could be eliminated, and missing features could be added. What is noteworthy is how reflective callbacks such as Module#method_added and Class#inherited make it possible to use the Ruby language to extend the Ruby language.
module Multi
module Arity
def method_added(name)
if @__multi_def__
@__multi_def__ = false # Disable method_added behavior while aliasing
arity = instance_method(name).arity
@__multi_methods__ |= [name]
class_eval "private :#{name}; alias __multi__#{name}__#{arity} #{name}"
@__multi_def__ = true
end
end
def multi
@__multi_methods__ = []
@__multi_def__ = true
yield
@__multi_def__ = false
@__multi_methods__.each do |name|
define_method(name) { |*args| send("__multi__#{name}__#{args.size}", *args) }
end
end
end
end
if __FILE__ == $0
require 'test/unit'
class Example
extend Multi::Arity
multi do
def hello
"Hello, somebody"
end
def hello(name)
"Hello, #{name}"
end
end
end
class MultiTest < Test::Unit::TestCase
def test_dispatch
ex = Example.new
assert_equal "Hello, somebody", ex.hello()
assert_equal "Hello, multimethods", ex.hello("multimethods")
end
end
end

Yep, cool stuff. I think this is basically what overload.rb does (or something like it).
Now, if you can get it to accept a type to simulate overloaded methods via static typing, I’ll be even more impressed. :)
I’m also curious how the various IDE’s out there would handle such code.
Dan
Multiple dispatch based on type could be implemented easily using the same pattern. One possible solution is to accept a type signature before a method’s implementation, like so:
The implementation would then dispatch to the first method whose formal arguments positionally match the message’s actual arguments. Using the case comparison operator would allow us to mix and match classes, regular expressions, and so forth.
Actually, that’s basically what overload.rb (and is similar to Ryan Pavlik’s strongtyping package).
I was hoping for this syntax:def hello "hello somebody" end def hello(String name) "hello #{name}" end def hello(Fixnum num) "hello number #{num}" endAlas, I don’t think it’s possible without yacc hacking.I read up on
overload.rband it is very similar. Also worth checking out is Topher Cyll’s multi gem. His article titled, If It’s Not Nailed Down, Steal It is also a good read on the topic.Also, you are right – I don’t believe the grammar for any existing implementation of Ruby could accomodate the traditional syntax for typing formal arguments. What makes these posts fun, however, is that we can easily use pure Ruby to implement the semantics, even though the syntax isn’t quite the same.