This post originated from an RSS feed registered with Ruby Buzz
by Jeremy Voorhis.
Original Post: Module#method_added
Feed Title: JVoorhis
Feed URL: http://feeds.feedburner.com/jvoorhis
Feed Description: JVoorhis is a Rubyist in northeast Ohio. He rambles about Ruby on Rails, development practices, other frameworks such as Django, and on other days he is just full of snark.
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