20080412

Multiple Inheritance is Evil, Even in Ruby

So yeah, I took a break for a while. According to Fallenearth's blog page, about 10 months. Wow. What happened in there? School, work, moving, lots of good video games, etc.. But the most influential thing in there that stopped me from coding was multiple inheritance. I'll get to that shortly.

Recently, Leia started playing a Korean MMO, and when I'm around someone who's doing something roughly like mudding, it makes me want to mud, too. And then I slap myself and remind myself that I'm creating one now, not playing one (I just don't have time for the latter). So that ultimately got me motivated to defeat multiple inheritance.

(as an aside, the blog editing page is unusable in IE8 Beta1. I'll have to file some bugs on that...)

When we last left our hero (me, 10 months ago), he (I) was struggling with an inheritance problem. As you may have gathered from previous posts, my still-unnamed mud is heavily object oriented, like everything else written with Ruby, I suppose. The concrete classes in the mud (Dealie, Mob, and company) mixin modules whenever possible, in a weak attempt at formalizing their interface to their callers.

As a concrete example, every dealie on the mud has a set of keywords that identify it in the mud world. These are typically specified in the XML area file, like so:

<dealie_prototype order="1" kind="food_proto">
    <lid area_name='main' local_id='10'/>
    <b><keywords>
        <keyword order="0">barn</keyword>
        <keyword order="1">cover</keyword>
        <keyword order="2">roof</keyword>
    </keywords></b>
    <short_description>the cover for a barn</short_description>
    <long_description>The spare roof of a barn is here.</long_description>
    <nourishment>10</nourishment>
</dealie_prototype>

Therafter, a player can refer to that item in their inventory, on the ground, and so on by using any substring of "barn", "cover", or "roof". By the way, we could theoretically extrapolate the right set of keywords by eliminating articles and such from the short description, but this gives more control to area authors, and that's generally a good thing.

Many very different things have the property of being identified by keywords in the mud world (Mob is another good example), so it makes sense to extract the common code for enabling this keyword identification into a separate module that all interested classes can mixin to get the functionality for free. In my mud, this class is called--stupidly, because I couldn't think of a better name--Keyworded.

The Keyworded module implements several things to enable this. It adds a data structure filled with the actual keywords (stupidly called ThingKeywords) as a member of the object. It provides some methods for matching an instance of the object against a keyword. Since this module has state (the list of keywords, basically), there needs to be a way to initialize the state. One way to do this is to define the initialize method in the Keyworded mixin. Callers who call super(keywords) at the top of their initialize method will pass on the keywords to the Keyworded initialize method. Great!

Not so fast! Callers often inherit from other modules and classes, and doing this means that all modules and classes in the method chain for initialize will get this spurious keywords parameter. Clearly, this won't work. I thought, "how would C++ handle this?" because I recently read Scott Meyers' Effective C++, for which I believe there cannot exist enough praise. Given that C++ is a language that fully supports multiple inheritance, surely it's solved this problem, right?

Indeed, it has. In C++, you don't call a single super method that does everything (pardon the pun), but instead the inheriting class calls the base class/interface constructors in a caller-specified way and order. Oh, if only Ruby supported some kind of syntax for targeting a specific class or module's method when calling it. I'm actually lameting; I don't think it has such a thing. Does it? Anybody?

Needless to say, the solution I ended up implementing is essentially trying to mimic the above. Each module that requires initialization, instead of defining initialize, defines the instance method Modulename_initialize (and Modulename_initialize_copy). Callers, like in C++, explicitly call the module "constructors" in whatever order they want, and with whatever parameters they want. Everybody goes home more or less happy, since as far as workarounds/hacks go, this one isn't so bad in that it follows another tried-and-tested language's pattern.

Having solved this, I went on to fix much more pressing things, like a load of bugs I found along the way, ranging from the minor syntax errors on code that just hadn't been covered yet, to corner cases in loading containers that contain other containers that contain the first one.

0 comments:

Post a Comment