Hi,

I've been using method_missing overly much in my code lately, and it's
prompted me to think a lot about it's limitations. I've been wishing
for a version of method_missing that allows the dynamic methods to act
more like they are real methods on the object. I think this could be
done by implementing a new method hook especially for dynamic methods:
dynamic_method.

dynamic_method would be called before method_missing if a method
lookup fails. If dynamic_method fails to handle the message,
method_missing will recieve the message for handling.

A couple of the immediate benefits afforded by a good implementation
of a dynamic method handler:
 - the object will respond_to? the method
 - you can call method(:foo) to get a copy of the dynamic method.

dynamic_method would be used something like this:

  class Foo
    def dynamic_method(name)
      if name.to_s =~ /^foo/
        # create the method (as a proc)
        return lambda do |*args|
          args.map{|arg| name.to_s.sub(/^foo/, arg.to_s) }
        end
      end
    end
  end

  f = Foo.new 
    ==>#<Foo:0x589a58>
  f.respond_to? :foobar
    ==>true
  f.respond_to? :barfoo
    ==>false
  f.foobar(*%w[one two three])
    ==>["onebar", "twobar", "threebar"]
  f.barfoo(*%w[one two three])
  NoMethodError: undefined method `barfoo' for #<Foo:0x589a58>
  
  f.method(:foobaz).call(*%w[one two three])
    ==>["onebaz", "twobaz", "threebaz"]

As you can see, a user-defined dynamic_method returns either a
callable object (Proc, Method, etc) or nil. If dynamic_method(message)
returns nil, it is assumed that the object does not
respond_to?(message), and method(message) should raise a
NoMethodError.



And here's a lightweight example implementation:

  module Kernel
    alias_method :old_method_missing, :method_missing
    def method_missing(name, *args, &block)
      m = dynamic_method(name)
      if m
        m.call(*args, &block)
      else
        m = Kernel.instance_method(:old_method_missing)
        m.bind(self).call(name, *args, &block)
      end
    end
    
    alias_method :old_respond_to?, :respond_to?
    def respond_to?(msg)
      (old_respond_to?(msg) || dynamic_method(msg)) ? true : false
    end
    
    alias_method :old_method, :method
    def method(name)
      old_method(name)
    rescue NameError => e
      m = dynamic_method(name)
      raise e unless m
      m
    end
    
    def dynamic_method(name)
      nil
    end
  end

Here's a lightweight version of OpenStruct written using dynamic_method:

  class OpenStruct
    def initialize(hash = {})
      @table = {}
      hash.each do |key, value|
        @table[key.to_sym] = value
      end
    end
    
    def dynamic_method(name)
      if name.to_s =~ /\=\z/
        lambda{|val| @table[name.to_s.chop.intern] = val }
      elsif @table.keys.include?(name)
        lambda{ @table[name] }
      end
    end
  end
  
And using it:

  os = OpenStruct.new
    ==>#<OpenStruct:0x511454 @table={}>
  os.red = 23
    ==>23
  os.blue = 42
    ==>42
  os.green = 56
    ==>56
  os.respond_to? :blue
    ==>true
  os.respond_to? :periwinkle
    ==>false
  os_blue = os.method(:blue)
    ==>#<Proc:0x0038743c@(eval):13>
  os.blue = 1024
    ==>1024
  os_blue.call
    ==>1024

This is not a complete idea (let alone implementation) at this time...
I just wanted to see if anyone had an opinion on whether the idea was
worth anything, or could make suggestions to improve the interface.

Now here's me second-guessing myself: The implementation is pretty
complicated; adding another dynamic message handler may not be worth
the confusion. It would be one more thing to explain to people, and
while method_missing is an elegant addition to a language, I'm not
sure this would be. Especially considering the need to add more
complexity to method lookups.

Still, I think that even if this idea here isn't worthy, putting it
out there might help someone else come up with a more elegant
solution.

So, any thoughts?

cheers,
Mark