From: James Coglan Date: 2009-03-18T03:18:54+09:00 Subject: Re: Dynamically extending modules once they have been included --000e0cd296ec2b954804655490b4 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit 2009/3/17 Synth > > > Far as I know, Ruby finds methods by getting a list of an object's > ancestors > > and extracting all implementations of the method from those modules. > Walking > > up a series of parent classes takes linear time so it's not expensive for > > classes to see stuff added to their parents, but walking and flattening a > > multiple inheritance tree could be exponential time so it makes sense to > > cache ancestor trees when things like #include are called, effectively > > blocking a class from seeing changes to its mixins' ancestry. > > Indeed, if every module had pointers to other included modules, I > definitely see how you'd end up with a very expensive object graph to > traverse to find method definitions. Using the wonders of ruby, I > found a (rudimentary)way you can easily implement this though(see > below). Its just a more generalized version of what I have above. If > the only consideration is performance, I don't think its that much of > a hit(depending on your ObjectSpace), plus when you do any > metaprogramming I don't think performance is a major concern. In this > case, any performance hit will occur only once at "include-time" and I > can't really envision injecting modules of code at any great rate - > although maybe this is just my short-sightedness :) Anywhere, here's > the workaround: > > class Module; > def included(base) > return unless(base.class == Module) > ObjectSpace.each_object(Class){|o| > next unless o.ancestors.include?(base) > o.send(:include, self) > } > end > end Cool. You'll probably want something to handle extended() as well, for where modules have been mixed into singleton classes. Calling object.extend(module) is effectively the same as: class << object include module end except that a different callback is called. For the curious, here's my implementation of the Ruby object model in JavaScript: http://github.com/jcoglan/js.class/tree/master/source/core/ It's reasonably documented; every module maintains both a list of its mixins and a list of its descendants as I need to propagate new methods down the inheritance chain. Each class has a module where it stores all its methods. Whenever an #include takes place, I run the tree and cache all the resulting methods on the class itself; I could probably do this for ancestry as well without running into Ruby's problems, since I can easily get modules to notify their descendants when they are modified so I can update all the cached method/ancestor tables. --000e0cd296ec2b954804655490b4--