12 messages in org.ruby-lang.ruby-talkadding a dynamic method handler? (lon...
FromSent OnAttachments
Mark HubbartFeb 15, 2005 11:03 pm 
Robert KlemmeFeb 16, 2005 1:19 am 
Mark HubbartFeb 16, 2005 12:52 pm 
Sam RobertsFeb 16, 2005 3:56 pm 
Robert KlemmeFeb 17, 2005 12:19 am 
Mark HubbartFeb 17, 2005 9:52 am 
Mark HubbartFeb 17, 2005 9:55 am 
Robert KlemmeFeb 18, 2005 12:39 am 
bennyFeb 18, 2005 8:29 am 
bennyFeb 18, 2005 8:29 am 
bennyFeb 18, 2005 8:29 am 
Mark HubbartFeb 18, 2005 5:04 pm 
Actions with this message:
Paste this link in email or IM:
Paste this link in email or IM:
Atom feed for this thread
Paste this URL into your reader:
Subject:adding a dynamic method handler? (long post)Actions...
From:Mark Hubbart (disc@gmail.com)
Date:Feb 15, 2005 11:03:48 pm
List:org.ruby-lang.ruby-talk

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