The set of expression classes is finite. In my extension to Runt, I
need to add some behavior to the TExpr module which I do with another
mixin. I didn’t want to have to worry about which classes mixin the
TExpr, I just wanted to modify(aka extend) the TExpr module like you
would any ordinary class and have it be reflected in all classes who
included this module. The alternative(which is what I’m ending up
doing) is to loop through all the classes of Runt and check the
ancestors for TExpr and if so, include my extension module directly
into the class. This approach seems yucky to me and not in the spirit
of ruby and really OO design.
Yes, this is very yucky and you shouldn’t have to do it. Unfortunately
I
can’t think of any elegant way to do this without inspecting the class
tree
using your method or ObjectSpace (you can’t get the descendants of a
module
directly, so I use ObjectSpace which is kinda expensive).
You can clearly argue with me whether you think extending a module
with another is appropriate or not in the above case. However, the
issue remains that this behavior is not consistent with the rest of
ruby. I point again to the fact that you can change behavior of a
module directly and it will be reflected by all included classes -
however NOT if you change behavior via including another module.
You ought to be able to just say you want to change all the objects of
such-and-such a type, whether that type is a module or a class. You
could of
course put your new methods straight into TExpr, which would work but
could
cause debugging and other monkey-patching issues. It’s up to you whether
you
consider this a problem.
Also, I don’t necessarily(keyword here) agree with the philosophical
point of needing ‘a lot’ of use cases to implement a feature.
I am very much speculating here, but I’d be surprised if this weren’t a
case
of, not implementing something as such, but rather one of removing
special
cases. One way to implement Ruby’s object system is that you construct
the
whole thing out of modules (a module is an object that has one method
table
and zero or more ‘parent’ modules), then things like parent-child
inheritance and singleton classes can be implemented on top of that.
This
ends up being quite elegant and I suspect this is a performance issue.
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.
If anyone knows how Module/Class are really implemented in MRI I’d love
to
know more about it (also: I really need to learn C).