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

Comments

  1. Daniel Berger said about 20 hours later:

    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

  2. JV said about 21 hours later:

    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:

    
    class Recognizer
      extend Multi::Type
      multi do
        deftype /[[:alpha:]][_[:alnum]]*/
        def recognize(token)
          puts "Found an identifier!" 
        end
    
        deftype /\d+(\.\d+)?/
        def recognize(token)
          puts "Found a number!" 
        end
    
        deftype String
        def recognize(token)
          puts "Unknown token!" 
        end
      end
    end
    

    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.

  3. Daniel Berger said 1 day later:

    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}" 
    end
    
    Alas, I don’t think it’s possible without yacc hacking.
  4. JV said 1 day later:

    I read up on overload.rb and 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.

(leave url/email »)