Narrowness

One candidate remains from the JSON example:

    multi to-json($d) {
        die "Can't serialize an object of type " ~ $d.WHAT.perl
    }

With no explicit type or constraint on the parameter $d, its type defaults to Any--and thus it matches any passed object. The body of this function complains that it doesn't know what to do with the argument. This works for the example, because JSON's specification covers only a few basic structures.

The declaration and intent may seem simple at first, but look closer. This final candidate matches not only objects for which there is no candidate defined, but it can match for all objects, including Int, Bool, Num. A call like to-json(2) has two matching candidates--Int and Any.

If you run that code, you'll discover that the Int candidate gets called. Because Int is a type that conforms to Any, it is a narrower match for an integer. Given two types A and B, where A conforms to B (A ~~ B, in Perl 6 code), an object which conforms to A does so more narrowly than to B. In the case of multi dispatch, the narrowest match always wins.

A successfully evaluated constraint makes a match narrower than a similar signature without a constraint. In the case of:

    multi to-json($d) { ... }
    multi to-json($d where {!defined $d}) { ... }

... an undefined value dispatches to the second candidate.

However, a matching constraint always contributes less to narrowness than a more specific match in the nominal type.

    TODO: Better example

    multi a(Any $x where { $x > 0 }) { 'Constraint'   }
    multi a(Int $x)                  { 'Nominal type' }

    say a(3), ' wins';       # says B<Nominal type wins>

This restriction allows a clever compiler optimization: it can sort all candidates by narrowness once to find the candidate with the best matching signature by examining nominal type constraints. These are far cheaper to check than constraint checks. Constraint checking occurs next, then the compiler considers the nominal types of candidates.

With some trickery it is possible to get an object which conforms to a built-in type (Num, for example) but which is also an undefined value. In this case the candidate that is specific to Num wins, because the nominal type check is narrower than the where {!defined $d} constraint.