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.