Multi-method dispatch, operators, and typecasting
I am suspicious of multi-method dispatch.
In a language like Smalltalk, when you send a message like
...you know that
In a language with multi-method dispatch,
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
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,
But let's say you define a new type to represent, say, money — we'll name it
Great. The + method you defined for all objects of type
However, if you write it
(which is, thanks to the commutative property, mathematically equivalent), what happens? The + method on the built-in class
The traditional Smalltalk solution to this is, in my opinion, a hack. It's called double-dispatch. The code in
(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.
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
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
you'd wind up having to resort to double-dispatch again. Your
Multi-method dispatch eliminates this problem entirely, by allowing your Money class to introduce an
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
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.
In a language like Smalltalk, when you send a message like
foo process: bar with: baz
...you know that
foo
will respond to the message, generally executing some method identified with process:with:
. bar
and baz
have no direct say.In a language with multi-method dispatch,
bar
and baz
— the arguments to the message — can also provide methods for handling the message. Generally, the most specific one is chosen at runtime: if foo
has a method for dealing with arguments of any two types, but baz
offers one that accepts the precise runtime types of all three objects, it is not foo
's code which will execute, but baz
's.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
baz
was 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
Money
runs, 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
Integer
fires up, and it may not know how to act on your Money
object — and almost certainly will not return a new Money
in response!The traditional Smalltalk solution to this is, in my opinion, a hack. It's called double-dispatch. The code in
Integer
would 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
asString
or asArray
methods 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
Money
class can easily handle "as: Integer", but having Integer
handle "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.
0 Comments:
Post a Comment
<< Home