Multi-method dispatch, operators, and typecasting
In a language like Smalltalk, when you send a message like
foo process: bar with: baz
...you know that
foowill respond to the message, generally executing some method identified with
bazhave no direct say.
In a language with multi-method dispatch,
baz— the arguments to the message — can also provide methods for handling the message. Generally, the most specific one is chosen at runtime: if
foohas a method for dealing with arguments of any two types, but
bazoffers one that accepts the precise runtime types of all three objects, it is not
foo's code which will execute, but
As I said, I'm suspicious of this.
I like the ease of reasoning that single-dispatch gives me, knowing that a given object's code will execute when I call a method. Particularly if the code for
bazwas loaded at runtime, the method may be effectively hijacked with a result I didn't intend.
However, I can think of two cases that are dramatically more elegant with MMD: operators, and casting (which is effectively an operator).
Smalltalk lets you name a method '+', to be called in the normal infix fashion,
a + b. In that fragment, it is of course
a's + method that will execute.
But let's say you define a new type to represent, say, money — we'll name it
Money. You teach it to add integers, floats, or other numbers using a + method. This is all well and good, so long as you write it like this:
myMoneyAmount + someInteger
Great. The + method you defined for all objects of type
Moneyruns, adds the integer, returns a
Money. All is well.
However, if you write it
someInteger + myMoneyAmount
(which is, thanks to the commutative property, mathematically equivalent), what happens? The + method on the built-in class
Integerfires up, and it may not know how to act on your
Moneyobject — and almost certainly will not return a new
The traditional Smalltalk solution to this is, in my opinion, a hack. It's called double-dispatch. The code in
Integerwould notice that the other addend isn't a known type, so it passes control into the addend using some other method. Effectively, it converts that fragment into something like
myMoneyAmount addedWithInteger: someInteger
(Why doesn't it flip it around and call the + method on the argument? Because that could quickly become an infinite loop of back-and-forth message sends.
addedWithInteger:, or whatever it's called, is defined as not performing double-dispatch.)
That's a simple solution for the built-in types, but if you've got two third-party types you want to add, each must sport an
addedWithWhatever:method for the other — when the third-party types are probably not aware of one anothers' existence. The situation can spiral out of control.
In Smalltalk, specifically, this problem is mitigated by the fact that you can add methods to other peoples' classes once they're loaded into the system. You can construct this web of adding methods to ensure your types play nice.
This is not, to me, a solution, but a workaround.
You'll run into similar problems if you try to implement a generic cast method. Smalltalks frequently have
asArraymethods to convert objects to some other type. If you wanted a generic method, like
foo as: Bar
you'd wind up having to resort to double-dispatch again. Your
Moneyclass can easily handle "as: Integer", but having
Integerhandle "as: Money" is more difficult.
Multi-method dispatch eliminates this problem entirely, by allowing your Money class to introduce an
as:method that is used when a Money object is the argument, not just the receiver. (Yes, pedants, in MMD there is no receiver per se.)
This is one situation where I would like the dispatch of the method to be determined at runtime.
C++ solves it a little differently, by having both member functions for operators, and free-floating functions that overload
operator+depending on the argument types, but the result is the same as MMD.
So, for this sort of problem, MMD is appealing. I've been thinking about this for a while, and there's an obvious place in the Mongoose language to hook MMD if necessary (namely, by providing implementations directly for the Class-independent Method Contract Identifier — effectively, C++'s approach).
I suspect there's a compromise, or perhaps an alternative technique.