From: James Coglan Date: 2009-03-17T19:11:12+09:00 Subject: Re: Dynamically extending modules once they have been included --000e0cd255781565f404654dd262 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit 2009/3/17 Synth > I get it now...the ancestors list of the module is copied over to the > including class at "include-time". If the module's ancestry is > changed, this will not be reflected. This seems to me to inhibit > some "meta-programmability"(if that's a word) as you can't mixin > modules to already included modules and have the effect take place > with whomever has already included the module. In other words, its > not consistent with the ability to dynamically change(add/change/ > delete) methods of the module directly at runtime. > > Has no one else encountered/wrestled with this quirk? Seems to me a > great way to organize code - a mixin for a mixin. This is very surprising, I always thought the ancestor tree was inspected at method call time, and I've done a fair amount of poking of Ruby's object system as I implemented it in JavaScript a while ago. I can only assume this is a performance hack since walking the ancestor tree is expensive and you sure don't want to be doing it for every method call, so it seems it's cached and refreshed when #include is called. Though, it's only refreshed from the module you've included, note the absence of K in the final line: >> module M; end => nil >> class C; include M; end => C >> C.ancestors => [C, M, Object, Kernel, BasicObject] >> module K; end => nil >> M.send :include, K => M >> C.ancestors => [C, M, Object, Kernel, BasicObject] >> module Z; end => nil >> C.send :include, Z => C >> C.ancestors => [C, Z, M, Object, Kernel, BasicObject] IMO this could be done better and preserve a bit more dynamism. In my JavaScript implementation, #include fully refreshes the method table for a class (it would include K in the above example) and caches included methods on the class itself to avoid tree lookups at method call time. If you call super(), the ancestor tree is always traversed to find ancestor methods, which is pretty expensive and makes super() much slower than normal method calls. Would be interested to hear other people's thoughts on this. It's kind of an edge case, but my opinion is that this behaviour is contrary to much of Ruby's dynamism, and is inconsistent: if you add methods to M, they become available to C, so why not new ancestors? Also, it means the ancestry tree looks different depending on who you ask. Having said that, experience tells me that "fixing" it would introduce a serious performance overhead for the whole language. -- James Coglan http://github.com/jcoglan --000e0cd255781565f404654dd262--