Yuval asked me off-list if I would comment on the various dispatch algorithms that have been proposed.
I guess I have used MMD more than most people in this discussion. Indeed, having both written Class::Multimethods and supervised a PhD that involved adding MMD to C++, one might have assumed that I've already "served my sentence" ;-). Nevertheless, all that experience has convinced me that the simpler the dispatch rules, the more usable the resulting MMD mechanism is. That's why I've consistently advocated uniform Manhattan distance over "left-most-best-fit". That's why I've always recommended an explicit C<is default> marker. That's why I was opposed to any particular numerical epsilon value. That's why I don't favour special treatment for junctions or multiple inheritance trees. The goal is always the same: to find a parameter list that most accurately matches the argument list, taking into account the type generalizations introduced by inheritance and the type specializations introduced by C<where> clauses. So, in my view the MMD mechanism ought to be something like: 1. Gather all visible variants with a compatible number of parameters (taking into account the requirements of any C<where> constraints) 2. If there are no such variants, throw a "no such multi" exception 3. Work out the Manhattan distance from the argument list to each variant's parameter list. 4. If there is a unique minimum, call that variant 5. Otherwise, discard every variant whose Manhattan distance isn't minimal 5. Work out the degree of specialization of each remaining argument list (i.e. the total number of C<where> specializations on the variant's complete set of parameters) 6. If there is a unique maximum, call that variant 7. Otherwise, if there is a compatible variant with an <is default> trait, call that variant 8. Otherwise, throw an "ambiguous call" exception. This is a much less dwimmy solution than Yuval's or Luke's, but it has the advantage that those eight steps reduce to eight words: Unique least-inherited most-specialized match, or default which will fit into most people's heads and still DWIM most of the time. Note that specializations enter into the decision process at two points: initially, they must be satisfied if the variant is to be considered "viable"; later, they are used as tie-breakers when resolving ambiguities. Using them to select the initial set of viable candidates is critical. If I have: multi sub foo (Int $x where { $^x < 10 }) {...} multi sub foo (Num $x) {...} then I almost certainly want a call to: foo(42); to successfully call the second variant, rather than throwing an exception like: Can't call multi sub foo (Int $x where { $^x < 10 }) when $x is 42. Keeping C<where> clauses as the sole "more specialized" marker also gives the developer *more* control than adding extra rules for junctions would. For example, someone might prefer to treat all junctions as being equally special: multi sub Foo(Int&Str) {...} multi sub foo(Int) {...} or treat junctions as more special: multi sub Foo((Int|Str) where Any) {...} multi sub foo(Int) {...} or treat junctions as less special: multi sub Foo(Int|Str) {...} multi sub foo(Int where Any) {...} In each case, these variations in "significance" are now explicitly and consistently marked. Damian