If the class in question doesn't have a suitable method, then the dispatch procedure searches upwards through the various ancestors of the original class, looking for an appropriate subroutine. If that search fails, the dispatch procedure attempts to invoke an AUTOLOAD subroutine somewhere in the invoking object's inheritance hierarchy.
The important point is that, whichever subroutine the method dispatcher eventually selects, it was all determined by the class of the original invoking object (i.e. according to the class of the first argument).
For most applications, the ability to select behaviour based on the type of a single argument is sufficient. However, some applications, such as the GUI event handler mentioned above, need to select the most applicable polymorphic method on the basis of more than one argument. This behaviour is known as multiple dispatch.
Generally speaking, multiple dispatch is needed whenever two or more objects belonging to different class hierarchies are going to interact, and we need to do different things depending on the combination of actual types of those objects. Typical applications that need this kind of ability include graphical user interfaces, image processing libraries, mixed-precision numerical computation systems, and most types of simulations.
It's possible to build "hand-crafted" multiply dispatched methods that look at the types of each of their arguments and react accordingly. For example, a normal (singly-dispatched) method could use ref or isa to determine the types of its other arguments and then select the correct behaviour in a cascaded if statement. Alternatively, it's possible to use hashes of hashes to set up a multidimensional table of subroutine references, then use the class names of the arguments (again found with ref) to index into it. Both these approaches are described in detail in [1,2].
The problem is that such hand-crafted mechanisms are complicated to construct and even harder to debug. And because every hand-built method is structurally similar, they're also tedious to code and maintain. Life would be very much easier if it were possible to define a set of identically named methods with distinct parameter lists, and then the program would "automagically" find the right one. Such a set of multiply dispatched methods is known as a multimethod, and each alternative method in the set is known as a variant.
But Perl has no mechanism for specifying parameter types, or for overloading subroutine names. And certainly there's no mechanism for automatically selecting between several (hypothetical) overloaded subroutines on the basis of the inheritance relationships of those (unspecifiable) parameter types.
Until now.
The new dispatch mechanism looks at the classes or types of each argument to the multimethod (by calling ref on each) and determines the "closest" matching variant of the multimethod, according to the parameter types specified in the variants' definitions (see below for a definition of "closest").
The result is something akin to C++'s function overloading but more sophisticated, since multimethods take the inheritance relationships of each argument into account. Another way of thinking of the mechanism is that it performs polymorphic dispatch on every argument of a method, not just on the first.
For example:
package LargeInt; @ISA = (LargeNum); package LargeFloat; @ISA = (LargeNum); package LargeNum; use Class::Multimethods; multimethod divide => (LargeInt, LargeInt) => sub { LargeInt::divide($_[0],$_[1]) }; multimethod divide => (LargeInt, LargeFloat), sub { LargeFloat::divide($_[0]->AsLargeFloat(), $_[1]); };This creates a (single) multimethod called LargeNum::divide with two variants. If the multimethod is called with two references to LargeInt objects as arguments, the first variant is invoked. If the multimethod is called with a LargeInt reference and a LargeFloat reference, the second variant is called.
Calling the multimethod with any other combination of LargeNum reference arguments (e.g. a reference to a LargeFloat and a reference to a LargeInt, or two LargeFloat references) results in an exception being thrown, with the message:
No viable candidate for call to multimethod LargeNum::divideTo avoid this, a "catch-all" variant could be specified:
multimethod divide => (LargeNum, LargeNum) => sub { LargeFloat::divide($_[0]->AsLargeFloat(), $_[1]->AsLargeFloat()); };Now, calling LargeNum::divide with (for example) a LargeFloat reference and a LargeInt reference causes this third variant to be invoked. That's because a LargeFloat is-a LargeNum (so the first argument is compatible with the first parameter), and a LargeInt is-a LargeNum too (so the second argument is compatible with the second parameter). Note that adding this third variant doesn't affect calls to the other two, since Class::Multimethods always selects the nearest match (see the next section for details of what nearest means).
This general "best fit" behaviour is extremely useful, because it means you can code the specific cases you want to handle (e.g. (LargeInt, LargeFloat)), and then provide one or more "catch-all" cases (e.g. (LargeNum, LargeNum)) to deal with any other combination of arguments. The multimethod automatically picks the most suitable variant to handle each actual argument list.
For example, the three variants for the divide multimethod shown above could all be defined in the LargeNum package, or the LargeFloat package or the LargeInt package, or in the main package, or anywhere else. They don't even have to be declared in the same package.
Of course, to enable a specific multimethod to be called within a given package, the package must know about it. That can be achieved by specifying just the name of the multimethod (i.e. with no argument list or variant code), much like a standard Perl subroutine declaration:
package Some::Other::Package; use Class::Multimethods; # import "divide" multimethod multimethod "divide";For convenience, the two steps can be consolidated, and the declaration abbreviated to:
package Some::Other::Package; use Class::Multimethods "divide";
With multimethods, since all arguments participate in the polymorphic resolution of a call (instead of just the first), it make no difference whether a multimethod is called as a method:
$num3 = $num1->divide($num2);or a subroutine:
$num3 = divide($num1, $num2);That means that Class::Multimethods also provides general subroutine overloading. For example:
package main; use IO; use Class::Multimethods; multimethod test => (IO::File, DataSource) => sub { $_[0]->print( $_[1]->data() ) }; multimethod test => (IO::Pipe, Queue) => sub { $_[0]->print( $_[1]->next() ) while $_[1]->count(); }; multimethod test => (IO::Socket, Stack) => sub { $_[0]->print( $_[1]->pop() ) }; # and later... test($some_handle, $some_data_ref);
multimethod stringify => (ARRAY) => sub { my @arg = @{$_[0]}; return "[" . join(", ",@arg) . "]"; }; multimethod stringify => (HASH) => sub { my %arg = %{$_[0]}; return "{" . join(", ", map( "$_=>$arg{$_}", keys %arg) ) . "}"; }; multimethod stringify => (CODE) => sub { return "sub {???}" }; # and later... print stringify([1,2,3]); print stringify({a=>1,b=>2,c=>3}); print stringify($array_or_hash_ref);In other words, the names of built-in types (i.e. those returned by ref) are perfectly acceptable as multimethod parameters. That's a nice bonus, but there's a problem. Because ref returns undef when given any literal string or numeric value, the following code:
$str = "a multiple dispatch oddity"; print stringify( 2001 ); print stringify( $str );will produce a nasty surprise:
No viable candidate for call to multimethod stringify() at line 1That's because the dispatch resolution process first calls ref(2001) to get the class name for the first argument, and therefore thinks it's of class undef. Since there's no stringify variant with undef as its parameter type, there are no viable targets for the multimethod call. Hence the exception.
To overcome this limitation, Class::Multimethods allows three special pseudo-type names within the parameter lists of multimethod variants. The first pseudo-type--'$'--is the class Class::Multimethods pretends any scalar value (except a reference) belongs to. Hence, the following definition makes the two recalcitrant stringifications of scalars work correctly:
multimethod stringify => ('$') => sub { return qq("$_[0]") };With that definition in place, the two calls:
print stringify( 2001 ); print stringify( $str );would produce:
"2001" "a multiple dispatch oddity"That solves the problem, but not as elegantly as it might. It would be better if numeric values were left unquoted. To this end, Class::Multimethods offers a second pseudo-type--"#"--which is the class it pretends numeric scalar values belong to. Hence, the following additional variant removes the quotes from stringified numbers:
multimethod stringify => ('#') => sub { return $_[0] };The final pseudo-type--"*"--is a wild-card or "don't care" type specifier, which matches any argument type exactly. For example, we could provide a "catch-all" stringify variant (to handle "GLOB" or "IO" references, for example):
multimethod stringify => ('*') => sub { croak "can't stringify a " . ref($_[0]); }Note that, even though the "*" variant matches any possible argument type, it does so with a greater inheritance distance than any other possible match. In other words, a "*" variant is a last resort, used only if every other variant is unviable.
print stringify( { a => [1,2,3], b => {b1=>4,b2=>5}, c => sub{3} } );will print out something like:
{a=>ARRAY(0x1001c23e), b=>HASH(0x10023ae6), c=>CODE(0x10027698)}because when the hash reference is passed to the HASH variant of stringify, each of its keys and values is interpolated directly into the returned string, rather than being individually stringified.
Fortunately a small tweak to the ARRAY and HASH variants solves the problem:
multimethod stringify => (ARRAY) => sub { my @arg = map { stringify($_) } @{$_[0]}; return "[" . join(", ",@arg) . "]"; }; multimethod stringify => (HASH) => sub { my %arg = map { stringify($_) } %{$_[0]}; return "{" . join(", ", map("$_=>$arg{$_}", keys %arg)) . "}"; };The difference here is that each element in the array or hash is recursively stringified (within the map operation) before the container itself is processed. And because stringify is a multimethod, there's no need for any special logic inside the map block to distinguish the various possible nested data types. Instead, the recursive calls automatically select the appropriate variant for each element, so nested references and values are correctly processed. So now the call:
print stringify( { a => [1,2,3], b => {b1=>4,b2=>5}, c => sub{3} } );prints:
{"a"=>[1, 2, 3], "b"=>{"b1"=>4, "b2"=>5}, "c"=>sub{???}}
class RoundPeg; @ISA = ( 'Peg' ); class SquareHole; @ISA = ( 'Hole' ); multimethod put_peg => (RoundPeg,Hole) => sub { print "round peg in hole\n" }; multimethod put_peg => (Peg,SquareHole) => sub { print "peg in square hole\n" }; multimethod put_peg => (Peg,Hole) => sub { print "a peg in a hole\n" };If put_peg is called like this:
my $peg = RoundPeg->new(); my $hole = SquareHole->new(); put_peg($peg, $hole);then Class::Multimethods can't dispatch the call, because it cannot decide between the variants (RoundPeg,Hole) and (Peg,SquareHole), each of which is the same inheritance distance (i.e. 1 derivation) from the actual arguments.
The default behaviour is to throw an exception like this:
Cannot resolve call to multimethod put_peg(RoundPeg,SquareHole). The multimethods: put_peg(RoundPeg,Hole) put_peg(Peg,SquareHole) are equally viableSometimes, however, the more specialized variants are only optimizations, and a more general variant (in this case, the (Peg,Hole) variant) would suffice as a default where such an ambiguity exists. In such situations, it's possible to tell Class::Multimethods to resolve the ambiguity by calling that general variant.
The resolve_ambiguous subroutine is automatically exported by Class::Multimethods and is used like this:
resolve_ambiguous put_peg => (Peg,Hole);That is, it takes the name of the multimethod being "disambiguated", and the parameter list of the variant that is to be the default for ambiguous cases. Of course, the specified variant must actually exist at the time of the call. If it doesn't, Class::Multimethod ignores it and throws the usual exception.
Alternatively, if no variant is suitable as a default, some other (non-multimethod) subroutine can be registered instead:
resolve_ambiguous put_peg => \&disambiguator;Now, whenever put_peg can't dispatch a call because it's ambiguous, disambiguator will be called instead, with the same argument list as put_peg was given.
Of course, resolve_ambiguous doesn't care what kind of subroutine it's given a reference to, so you can also use an anonymous subroutine:
resolve_ambiguous put_peg => sub { print "can't put a ", ref($_[0]), " into a ", ref($_[1]), "\n"; };Dispatch can also fail if there are no suitable variants available to handle a particular call. For example:
my $peg = JPEG->new(); my $hole = Loophole->new(); put_peg($peg, $hole);which would normally produce the exception:
No viable candidate for call to multimethod put_peg(JPEG,Loophole)since classes JPEG and Loophole aren't in the Peg and Hole hierarchies, so there's no inheritance path back to a more general variant.
The resolve_no_match subroutine, which is also exported from Class::Multimethods, can be used to set up a handler for such cases. For example:
resolve_no_match put_peg => sub { my ($p, $h) = map {ref} @_; $_[0]->display($_[1]) if $p =~ /[JM]PEG/; call_plumber() if $p eq 'ClothesPeg' && $h eq 'DrainHole'; # etc. };As with resolve_ambiguous, the variant or subroutine registered with resolve_no_match is called with the same set of arguments that were passed to the original multimethod call.
Class::Multimethods::analyse("test");will print out an analysis of the dispatch behaviour for all possible combinations of an IO::File, IO::Pipe, or IO::Socket object (as the first argument), and a DataSource, Queue, or Stack object (as the second argument). Furthermore analyse will examine the class hierarchies of which these classes are a part, and generate test cases for any ancestral or descendant classes as well. For instance, for the first argument it will also test objects of the classes IO::Handle, and IO::Seekable, (since these are both ancestral classes of IO::File), and for the second argument it might also test objects of the classes PriorityQueue and FixedLengthQueue, if these where derived from the Queue class.
The analyse method iterates through every possible combination of argument types and reports which variant (if any) would have been called for that set of arguments. Combinations that result in ambiguities or failure to dispatch are reported separately. Even more usefully, for argument sets where a single variant would be successfully dispatched, analyse also reports any other viable candidates (i.e. other variants that could handle the call, but which were at a greater inheritance distance from the argument list, and so not selected). This can be especially useful in determining why a particular variant was not called as expected.
The Class::Multimethods module enables variants of a multimethod to be declared and used, either as object methods or as independent, overloaded subroutines. It provides a sophisticated breadth-first dispatch resolution mechanism and allows the implementor to dictate resolution strategies when dispatch would normally fail.
The module is available from the CPAN.