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.
···
2009/3/17 Synth <librarising@gmail.com>
--
James Coglan