Class:...methods(3pm)
π Class::Multimethods(3pm) β the manual, bestie edition π
okay so. hi. you're here because you want multimethods in Perl and honestly? taste. this is the Class::Multimethods page, and I'm gonna walk you through it like we're texting at 2am. every flag, every warning, every code block β all of it stays. no cap. π
β¨ NAME β¨
Class::Multimethods β support multimethods and function overloading in Perl π¦
πΈ VERSION πΈ
this doc describes version 1.701 of Class::Multimethods, released April 9, 2000. yes it's vintage. yes it still ate. π
π SYNOPSIS π
# IMPORT THE multimethod DECLARATION SUB...
use Class::Multimethods;
# DECLARE VARIOUS MULTIMETHODS CALLED find...
# 1. DO THIS IF find IS CALLED WITH A Container REF AND A Query REF...
multimethod find => (Container, Query)
=> sub { $_[0]->findquery($_[1]) };
# 2. DO THIS IF find IS CALLED WITH A Container REF AND A Sample REF...
multimethod find => (Container, Sample)
=> sub { $_[0]->findlike($_[1]) };
# 3. DO THIS IF find IS CALLED WITH AN Index REF AND A Word REF...
multimethod find => (Index, Word)
=> sub { $_[0]->lookup_word($_[1]) };
# 4. DO THIS IF find IS CALLED WITH AN Index REF AND A qr// PATTERN
multimethod find => (Index, Regexp)
=> sub { $_[0]->lookup_rx($_[1]) };
# 5. DO THIS IF find IS CALLED WITH AN Index REF AND A NUMERIC SCALAR
multimethod find => (Index, '#')
=> sub { $_[0]->lookup_elem($_[1]) };
# 6. DO THIS IF find IS CALLED WITH AN Index REF AND A NON-NUMERIC SCALAR
multimethod find => (Index, '$')
=> sub { $_[0]->lookup_str($_[1]) };
# 7. DO THIS IF find IS CALLED WITH AN Index REF AND AN UNBLESSED ARRAY REF
# (NOTE THE RECURSIVE CALL TO THE find MULTIMETHOD)
multimethod find => (Index, ARRAY)
=> sub { map { find($_[0],$_) } @{$_[1]} };
# SET UP SOME OBJECTS...
my $cntr = new Container ('./datafile');
my $indx = $cntr->get_index();
# ...AND SOME INHERITANCE...
@BadWord::ISA = qw( Word );
my $badword = new BadWord("fubar");
# ...AND EXERCISE THEM...
print find($cntr, new Query('cpan OR Perl')); # CALLS 1.
print find($cntr, new Example('by a committee')); # CALLS 2.
print find($indx, new Word('sugar')); # CALLS 3.
print find($indx, $badword); # CALLS 3.
print find($indx, qr/another brick in the Wall/); # CALLS 4.
print find($indx, 7); # CALLS 5.
print find($indx, 'But don't do that.'); # CALLS 6.
print find($indx, [1,"one"]); # CALLS 7,
# THEN 5 & 6.
like. look at that. seven whole variants of one find and it just picks the right one. iconic behaviour. π
π₯ DESCRIPTION π₯
the Class:Multimethod module exports a subroutine (&multimethod) that you can use to declare other subroutines, and those subroutines get dispatched using an algorithm that is not the normal Perl subroutine-or-method dispatch. she's different. she's special. π
so, normal Perl subroutines? dispatched by finding the appropriately-named subroutine in the current package (or a specified package) and calling it. that's it.
normal Perl methods? dispatched by trying to find the appropriately-named subroutine in the package the invoking object is blessed into, or β failing that β recursively searching for it in the packages listed in the relevant @ISA arrays.
Class::Multimethods multimethods? completely different vibe. the dispatch mechanism looks at the classes or types of each argument to the multimethod (by calling ref on each one) and figures out the "closest" matching variant of the multimethod, based on the argument types you specified in the variants' definitions. (if you wanna know exactly what "closest" means, go read "Finding the "nearest" multimethod" below, bestie, it's all there π)
the result is kinda like C++'s function overloading, except smarter β because multimethods actually take the inheritance relationships of each argument into account. another way to think about it: it does polymorphic dispatch on every argument of a method, not just the first one. main character energy fr fr. π
π Defining multimethods π
the Class::Multimethods module exports a subroutine called multimethod, and you use it to specify multimethod variants with the dispatch behaviour described above. multimethod takes:
- the name of the desired multimethod
- a list of class names
- a subroutine reference
β¦and it generates a corresponding multimethod variant within the current package. cute, right? π
for example, this declaration:
package LargeInt; @ISA = (LargeNumeric);
package LargeFloat; @ISA = (LargeNumeric);
package LargeNumeric;
use Class::Multimethods;
multimethod divide => (LargeInt, LargeInt) => sub
{
LargeInt::divide($_[0],$_[1]);
};
multimethod divide => (LargeInt, LargeFloat) => sub
{
LargeFloat::divide($_[0]->AsLargeFloat(),$_[1]));
};
creates a (single!) multimethod &LargeNumeric::divide with two variants. if the multimethod gets called with two references to LargeInt objects as arguments, the first variant (i.e. that anonymous subroutine) is invoked. if it's called with a LargeInt reference and a LargeFloat reference, the second variant gets called. two variants, one function name, no chaos. slay. β¨
okay heads up though π¨ if you're running under use strict, the list of bareword class names in each variant definition is gonna cause problems. strict does not play about barewords.
in that case you'll need to say:
multimethod divide => ('LargeInt', 'LargeInt') => sub
{
LargeInt::divide($_[0],$_[1]);
};
multimethod divide => ('LargeInt', 'LargeFloat') => sub
{
LargeFloat::divide($_[0]->AsLargeFloat(),$_[1]));
};
or better still:
multimethod divide => qw( LargeInt LargeInt ) => sub
{
LargeInt::divide($_[0],$_[1]);
};
multimethod divide => qw( LargeInt LargeFloat ) => sub
{
LargeFloat::divide($_[0]->AsLargeFloat(),$_[1]));
};
or best of all (;-)):
{
no strict;
multimethod divide => (LargeInt, LargeInt) => sub
{
LargeInt::divide($_[0],$_[1]);
};
multimethod divide => (LargeInt, LargeFloat) => sub
{
LargeFloat::divide($_[0]->AsLargeFloat(),$_[1]));
};
}
so. if you call that multimethod with any other combination of LargeNumeric reference arguments β like a reference to a LargeFloat and a reference to a LargeInt, or two LargeFloat references β an exception gets thrown, with the message:
No viable candidate for call to
multimethod LargeNumeric::divide at ...
π not the exception. to avoid that whole situation, you could provide a "catch-all" variant:
multimethod divide => (LargeNumeric, LargeNumeric) => sub
{
LargeFloat::divide($_[0]->AsLargeFloat(),$_[1]->AsLargeFloat));
}
now calling &LargeNumeric::divide with either a LargeFloat reference and a LargeInt reference, or with two LargeFloat references, means this third variant gets invoked. π«Ά and here's the good part: adding this third alternative doesn't affect calls to the other two, because Class::Multimethods always picks the "nearest" match. (details in "Finding the "nearest" multimethod" below, iykyk.)
this "best fit" behaviour is extremely useful, because it means you can code up the specific cases you actually care about, plus one or more "catch-all" cases to deal with any other combination of arguments. chef's kiss. π©βπ³π
π Finding the "nearest" multimethod π
of course, the usefulness of this whole system depends on how intelligently Class::Multimethods decides which version of a multimethod is "nearest" to the set of arguments you handed it. this decision process is called "dispatch resolution", and Class::Multimethods does it like this:
- if the types of the arguments given (as determined by
ref) exactly match the types specified in any variant of the multimethod, that variant is the one called. no debate. periodt. π - otherwise,
Class::Multimethodscompiles a list of "viable targets". a viable target is a variant of the multimethod with the correct number of parameters, such that for each parameter, the specified parameter type is a base class of the actual type of the corresponding argument in the actual call. - if there's only one viable target, it's immediately called. if there are no viable targets, an exception gets thrown telling you that. π
- otherwise,
Class::Multimethodexamines each viable target and computes its "distance" to the actual set of arguments. the distance of a target is the sum of the distances of each of its parameters. and the distance of an individual parameter is the number of inheritance steps between its class and the actual class of the corresponding argument.
hence β if a specific argument is of the same class as the corresponding parameter type, the distance to that parameter is zero. if the argument is of a class that's an immediate child of the parameter type, the distance is 1. if the argument is of a class which is a "grandchild" of the parameter type, the distance is 2. Et cetera. πͺ
Class::Multimethodthen chooses the viable target with the smallest "distance" as the "final target". if there's more than one viable target with an equally smallest distance, an exception is thrown indicating the call is ambiguous. if there's only a single final target,Class::Multimethodrecords its identity (so the distance computations don't have to be repeated the next time the same set of argument types shows up), and then calls that final target. π―
π Where to define multimethods π
Class::Multimethods genuinely does not care which packages the individual variants of a multimethod are defined in. every variant of a multimethod is visible to the underlying multimethod dispatcher, no matter where it was defined. she's flexible like that. π§
for example, those three variants for the divide multimethod up there? they could all be defined in the LargeNumeric package, or the LargeFloat package, or the LargeInt package, or in main, or in a separate package of their very own. wherever you want. π‘
of course, to make a specific multimethod visible within a given package you still need to tell that package about it. you can do that by specifying the name of the multimethod only β i.e. no argument list and no variant code:
package Some::Other::Package::That::Wants::To::Use::divide;
use Class::Multimethods;
multimethod "divide";
and for convenience, that declaration itself can be abbreviated to:
package Some::Other::Package::That::Wants::To::Use::divide;
use Class::Multimethods "divide";
look at that. one word in the import list and boom, you're in. β¨
Similarly, Class::Multimethod genuinely does not care whether multimethods get called as methods or as regular plain subroutines. π Which is wildly different from normal Perl methods and subroutines, where the way you call them is what decides how they get dispatched. Like, the call itself is the whole vibe check.
With multimethods though, every argument gets a say in the polymorphic resolution of a call (not just the first one, bestie), so it makes zero difference whether a multimethod is called as a subroutine:
numref3 = divide($numref1, $numref2);
or as a method:
numref3 = $numref1->divide($numref2);
(so long as the multimethod has been declared in the appropriate place: the current package for subroutine-like calls, or the invoking object's package for method-like calls).
In other words, Class::Multimethods also provides general subroutine overloading. Periodt. For example:
package main;
use IO;
use Class::Multimethods;
multimethod debug => (IO::File) => sub
{
print $_[0] "This should go in a file\n";
}
multimethod debug => (IO::Pipe) => sub
{
print $_[0] "This should go down a pipe\n";
}
multimethod debug => (IO::Socket) => sub
{
print $_[0] "This should go out a socket\n";
}
# and later
debug($some_io_handle);
β¨ Non-class types as parameters β¨
Yet another thing Class::Multimethods is completely unbothered by: whether the parameter types for each multimethod variant are the names of "real" classes, or just the identifiers you get back when raw Perl data types get passed to the built-in ref function. π¦ So yeah, you could totally define multimethod variants like this:
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] ), "\n";
print stringify( {a=>1,b=>2,c=>3} ), "\n";
print stringify( $array_or_hash_ref ), "\n";
As long as you remember that the parameter types ARRAY, HASH, and CODE really mean "reference to array", "reference to hash", and "reference to subroutine", the names of built-in types (i.e. those returned by ref) are perfectly acceptable as multimethod parameters. π
That's a cute lil bonus, but there's a problem, and it's giving plot twist. π Because ref returns an empty string when it's given any literal string or numeric value, this code:
print stringify( 2001 ), "\n";
print stringify( "a multiple dispatch oddity" ), "\n";
will produce a nasty surprise:
No viable candidate for call to multimethod stringify() at line 1
That'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 "". And since there's no stringify variant with an empty string 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 that Class::Multimethods pretends that any scalar value (except a reference) belongs to. So you can make those two recalcitrant stringifications of scalars work by defining:
multimethod stringify => ("$")
=> sub { return qq{"$_[0]"} }
With that definition in place, the two calls:
print stringify( 2001 ), "\n";
print stringify( "a multiple dispatch oddity" ), "\n";
would produce:
"2001"
"a multiple dispatch oddity"
That solves the problem, just... 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 (where a scalar value is "numeric" if it's truly a numerical value, without implicit coercions):
$var = 0 # numeric --> '$'
$var = 0.0 # numeric --> '$'
$var = "0"; # string --> '#'
Hence you could now also define:
multimethod stringify => ("#")
=> sub { return "+$_[0]" }
and the two calls to &stringify now produce:
+2001
"a multiple dispatch oddity"
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]) }
The "*" pseudo-type can also be used in multiple-argument multimethods. For example:
# General case...
multimethod handle => (Window, Event, Mode)
=> sub { ... }
# Special cases...
multimethod handle => (MovableWindow, MoveEvent, NormalMode)
=> sub { ... }
multimethod handle => (ScalableWindow, ResizeEvent, NormalMode)
=> sub { ... }
# Very special case
# (ignore any event in any window in PanicMode)
multimethod handle => ("*", "*", PanicMode)
=> sub { ... }
π Resolving ambiguities and non-dispatchable calls π
It's honestly pretty easy to set up a multimethod such that particular combinations of argument types cannot be correctly dispatched. π For example, consider the following variants of a multimethod called put_peg:
multimethod put_peg => (RoundPeg,Hole) => sub
{
print "a round peg in any old hole\n";
};
multimethod put_peg => (Peg,SquareHole) => sub
{
print "any old peg in a square hole\n";
};
multimethod put_peg => (Peg,Hole) => sub
{
print "any old peg in any old hole\n";
};
If put_peg is called like so:
put_peg( RoundPeg->new(), SquareHole->new() );
then Class::Multimethods can't dispatch the call, because it cannot decide between the (RoundPeg,Hole) and (Peg,SquareHole) variants, each of which is the same "distance" (i.e. 1 derivation) from the actual arguments. Like, it's a genuine tie. π
The default behaviour is to throw an exception (i.e. die) like this:
Cannot resolve call to multimethod put_peg(RoundPeg,SquareHole).
The multimethods:
put_peg(RoundPeg,Hole)
put_peg(Peg,SquareHole)
are equally viable at ...
Sometimes, however, the more specialized variants are only optimizations, and a more general case (e.g. the (Peg,Hole) variant) would suffice as a default where such an ambiguity exists. If that's the case, it's possible to tell Class::Multimethods to resolve the ambiguity by calling that variant, using the resolve_ambiguous subroutine. resolve_ambiguous is automatically exported by Class::Multimethods and is used like this:
resolve_ambiguous put_peg => (Peg,Hole);
That is, you specify the name of the multimethod being disambiguated, and the signature of the variant to be used in 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, you can register a reference to a subroutine that is to be called 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 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:
put_peg( JPEG->new(), Loophole->new() );
which would normally produce the exception:
No viable candidate for call to
multimethod put_peg(JPeg,Loophole) at ...
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.
To handle cases like this, you can use the resolve_no_match subroutine, which is also exported from Class::Multimethods. resolve_no_match registers a multimethod variant, or a reference to some other subroutine, that is then used whenever the dispatch mechanism can't find a suitable variant for a given multimethod call.
For example:
resolve_no_match put_peg
=> sub
{
put_jpeg(@_)
if ref($_[0]) eq 'JPEG';
shift()->hang(@_)
if ref($_[0]) eq 'ClothesPeg';
hammer(@_)
if ref($_[0]) eq 'TentPeg';
# etc.
};
As with resolve_ambiguous the registered variant or subroutine is called with the same set of arguments that were passed to the original multimethod call. π
πΈ Redispatching multimethod calls πΈ
Sometimes a polymorphic method in a derived class is used to add functionality to an inherited method. For example, a derived class's print_me method might call its base class's print_me, making use of Perl's special $obj->SUPER::method() construct:
class Base;
sub print_me
{
my ($self) = @_;
print "Base stuff\n";
}
class Derived; @ISA = qw( Base );
sub print_me
{
my ($self) = @_;
$self->SUPER::print_me(); # START LOOKING IN ANCESTORS
print "Derived stuff\n";
}
Applying superclass to the argument of your multimethod is basically you telling Class::Multimethods "bestie, start hunting for parameter types up in Derived's ancestors, k?" π
That's the whole vibe.
Also, and this is not even a multimethod thing β regular Perl lets you explicitly tell the polymorphic dispatcher where to start looking, just by fully qualifying the method name:
sub Derived::print_me
{
my ($self) = @_;
$self->Base::print_me(); # START LOOKING IN Base CLASS
print "Derived stuff\n";
}
And guess what β you can do the exact same thing with multimethods π superclass takes an optional second argument that tells Class::Multimethods exactly where to start looking:
multimethod print_me => (Derived) => sub
{
my ($self) = @_;
print_me( superclass($self => Base) ); # START LOOKING IN Base
print "Derived stuff\n";
}
Now here's the lowkey iconic part π: unlike regular method calls, with multimethods you can slap that superclass subroutine on any or all of a multimethod's arguments. Not one, not some β any or all. For example:
multimethod handle => (MovableWindow, MoveEvent, NormalMode) => sub
{
my ($w, $e, $m) = @_;
# Do any special stuff,
# then redispatch to more general handler...
handle(superclass($w), $e, superclass($m => Mode) );
}
In this case the redispatch would start looking for variants that match (any of MovableWindow's ancestors, MoveEvent, Mode). See what she did there? β¨
And fr fr, please remember this one: just like with regular methods, the class of the actual arguments does not change just because we subverted the dispatch sequence. So if that redispatch above called the handle variant that takes arguments (Window, MoveEvent, Mode), the actual arguments would still be of types (MovableWindow, MoveEvent, NormalMode). No identity theft here, the args are who they are π
π DIAGNOSTICS π
If you call multimethod and forget to hand over a code reference as the last argument, she dies on you with:
"multimethod: last arg must be a code reference at %s"
If the dispatch mechanism can't find any multimethod whose signature matches the actual arguments, it dies with:
"No viable candidate for call to multimethod %s at %s"
If the dispatch mechanism finds two or more multimethods whose signatures are equally "close" to the actual arguments (peep "The dispatch resolution process"), it dies with:
"Cannot resolve call to multimethod %s. The multimethods:
%s
are equally viable at %s"
And if you specify two variants with the same parameter lists, Class::Multimethods warns:
"Multimethod %s redefined at %s"
β¦but only if $^W is true (like, when you're running under the -w flag). No -w, no warning, iykyk π«Ά
π AUTHOR π
Damian Conway (damian@conway.org)
π BUGS AND IRRITATIONS π
There are undoubtedly serious bugs lurking somewhere in code this complex :-) Bug reports and other feedback are most welcome. Please be nice, she's trying her best π
Ongoing annoyances include:
- The module uses
qr//constructs to improve performance. Hence it won't run under Perls earlier than 5.005. Not a vibe for the retro Perl girlies π - Multimethod dispatch is much slower than regular dispatch when the resolution has to resort to the more generic cases (though it's actually as very nearly as fast as doing the equivalent type resolution "by hand", and certainly more reliable and maintainable). Slower, yes β but tidier, bestie πΈ
- The cache management is far too dumb. Adding any new multimethod clobbers the entire cache, when it should only expunge those entries "upstream" from the the new multimethod's actual parameter types.
It's unclear, however, under what circumstances the expense of a more careful cache correction algorithm would ever be recouped by the savings in dispatch (well, obviously, when the installion of multimethods is a rare event and multimethod dispatching is frequent, but where is the breakeven point?)
π COPYRIGHT π
Copyright (c) 1998-2000, Damian Conway. All Rights Reserved.
This module is free software. It may be used, redistributed
and/or modified under the terms of the Perl Artistic License
(see http://www.perl.com/perl/misc/Artistic.html)
And that's a wrap, bestie π This whole page is from perl v5.36.0, dated 2022-10-13, and it's Class:...methods(3pm) β go forth and dispatch, you absolute main character πβ¨
π¦ where this page comes from
- ubuntu noble: libclass-multimethods-perl 1.701-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libclass-multimethods-perl 1.701-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libclass-multimethods-perl 1.701-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms