From: Charles Oliver Nutter Date: 2011-04-13T07:13:25+09:00 Subject: [ruby-core:35729] Re: caching of the ancestor chain On Tue, Apr 12, 2011 at 3:53 AM, Xavier Noria wrote: > Since MRI inserts proxies in the ancestor chain that point to the > actual modules and adjusts the parent pointers to have a linear > ancestor chain (by linear I mean that you follow the pointers up and > you're done), you get this ... > �� ��p C.ancestors # => [C, M, Object, Kernel, BasicObject] > �� ��p M.ancestors # => [M, N] > > That linear ancestor chain is not updated if the ancestors chain of > involved modules is modified. That's what I meant by "caching". Ok, I understand. The llist of modules that module M includes is copied in only at include time, and additional changes are not made visible to already-included classes. > Question was whether this was a conscious design/implementation > decision on behalf of speed. I think the problem is less about perf and more about maintaining a simple logical structure for the superclass hierarchy. With the current mechanism, modules are included in a well-defined order and live in their own slots in the class hierarchy. Those slots are occupied by a wrapper that delegates method lookup to the module itself. Including a module that includes other modules will simply also include those modules as they exist at include time. In order to make already-included modules incorporate new module includes done later, modules would need to have a pointed to their insertion point in the hierarchy and be able to make new modifications to that hierarchy upon change. Unlike method table updates, which largely "just happen" due to the method table delegation, this would have to be an *active* change, since the class hierarchy would need to reflect the new modules. Now this is certainly possible, but no implementation other than JRuby has the logic in place to support it (not that it's complicated logic...we're just the only one to have it). In JRuby, each Module references (weakly) all insertion points in all hierarchies. This was done to facilitate method-cache invalidation, but also makes your case possible too. With a small patch to JRuby: diff --git a/src/org/jruby/RubyModule.java b/src/org/jruby/RubyModule.java index a6470b4..7583666 100644 --- a/src/org/jruby/RubyModule.java +++ b/src/org/jruby/RubyModule.java @@ -2018,6 +2018,11 @@ public class RubyModule extends RubyObject { modules[i].callMethod(context, "append_features", this); modules[i].callMethod(context, "included", this); } + + // if we've been included elsewhere, make those hierarchies include us too + for (RubyClass cls : includingHierarchies) { + cls.include(modules); + } return this; } Your script works as you would expect: ~/projects/jruby ��� jruby ancestor_thing.rb [C, M, N, Object, Kernel] [M, N] I have not thought through the implications of such a change, yet. - Charlie