NAME
Sub::MultiMethod - yet another implementation of multimethods
SYNOPSIS
How to generate JSON (albeit with very naive string quoting) using
multimethods:
use v5.20;
use strict;
use warnings;
use experimental 'signatures';
package My::JSON {
use Moo;
use Sub::MultiMethod qw(multimethod);
use Types::Standard -types;
multimethod stringify => (
positional => [ Undef ],
code => sub ( $self, $undef ) {
return 'null';
},
);
multimethod stringify => (
positional => [ ScalarRef[Bool] ],
code => sub ( $self, $bool ) {
return $$bool ? 'true' : 'false';
},
);
multimethod stringify => (
alias => "stringify_str",
positional => [ Str ],
code => sub ( $self, $str ) {
return sprintf( q<"%s">, quotemeta($str) );
},
);
multimethod stringify => (
positional => [ Num ],
code => sub ( $self, $n ) {
return $n;
},
);
multimethod stringify => (
positional => [ ArrayRef ],
code => sub ( $self, $arr ) {
return sprintf(
q<[%s]>,
join( q<,>, map( $self->stringify($_), @$arr ) )
);
},
);
multimethod stringify => (
positional => [ HashRef ],
code => sub ( $self, $hash ) {
return sprintf(
q<{%s}>,
join(
q<,>,
map sprintf(
q<%s:%s>,
$self->stringify_str($_),
$self->stringify( $hash->{$_} )
), sort keys %$hash,
)
);
},
);
}
my $json = My::JSON->new;
say $json->stringify( {
foo => 123,
bar => [ 1, 2, 3 ],
baz => \1,
quux => { xyzzy => 666 },
} );
While this example requires Perl 5.20+, Sub::MultiMethod is tested and
works on Perl 5.8.1 and above.
DESCRIPTION
Sub::MultiMethod focusses on implementing the dispatching of multimethods
well and is less concerned with providing a nice syntax for setting them
up. That said, the syntax provided is inspired by Moose's `has` keyword
and hopefully not entirely horrible.
Functions
Sub::MultiMethod exports nothing by default. You can import the functions
you want by listing them in the `use` statement:
use Sub::MultiMethod "multimethod";
You can rename functions:
use Sub::MultiMethod "multimethod" => { -as => "mm" };
You can import everything using `-all`:
use Sub::MultiMethod -all;
Sub::MultiMethod also offers an API for setting up multimethods for a
class, in which case, you don't need to import anything.
`multimethod $name => %spec`
The specification supports the same options as Type::Params v2 to specify
a signature for the method, plus a few Sub::MultiMethod-specific options.
Any options not included in the list below are passed through to
Type::Params. (The options `goto_next`, `on_die`, `message`, and `want_*`
are not supported.)
`code` *(CodeRef)*
Conceptually required, but see the `return` and `die` shortcuts.
The sub to dispatch to. It will receive parameters in @_ as you would
expect, but these parameters have been passed through the signature
already, so will have had defaults and coercions applied.
An example for positional parameters:
code => sub ( $self, $prefix, $match, $output ) {
print { $output } $prefix;
...;
},
An example for named parameters:
code => sub ( $self, $arg ) {
print { $arg->output } $arg->prefix;
...;
},
Note that $arg is an object with methods for each named parameter.
Corresponding examples for older versions of Perl without signature
support.
code => sub {
my ( $self, $prefix, $match, $output ) = @_;
print { $output } $prefix;
...;
},
And:
code => sub {
my ( $self, $arg ) = @_;
print { $arg->output } $arg->prefix;
...;
},
`return` *(Value)*
Shortcut.
You can use `return => "foo"` as a shortcut for `code => sub { return
"foo" }`. This cannot be used to return refereneces.
`die` *(CodeRef|Str)*
Shortcut.
You can use `die => "foo"` as a shortcut for `code => sub { require
Carp; Carp::croak("foo") }`.
You can use `die => \&foo` as a shortcut for `code => sub { require
Carp; Carp::croak(foo()) }`.
`signature` *(CodeRef)*
Optional.
If `signature` is set, then Sub::MultiMethod won't use Type::Params to
build a signature for this multimethod candidate. It will treat the
coderef as an already-built signature.
A coderef signature is expected to take @_, throw an exception if the
arguments cannot be handled, and return @_ (possibly after some
manipulation).
`alias` *(Str|ArrayRef[Str])*
Optional.
Installs an alias for the candidate, bypassing multimethod dispatch.
(But not bypassing the checks, coercions, and defaults in the
signature!)
`method` *(Bool)*
Optional, defaults to 1.
Indicates whether the multimethod should be treated as a method (i.e.
with an implied $self). Defaults to true, but `method => 0` can be
given if you want multifuncs with no invocant.
Multisubs where some candidates are methods and others are non-methods
are not currently supported! (And probably never will be.)
`want` *(Str|ArrayRef)*
Optional.
Allows you to specify that a candidate only applies in certain
contexts. The context may be "VOID", "SCALAR", or "LIST". May
alternatively be an arrayref of contexts. "NONVOID" is a shortcut for
`["SCALAR","LIST"]`. "NONLIST" and "NONSCALAR" are also allowed.
`if` *(CodeRef)*
Optional.
Allows you to specify that a candidate only applies in certain
conditions.
if => sub { $ENV{OSTYPE} eq 'linux' },
The coderef is called with no parameters. It has no access to the
multimethod's @_.
`score` *(Int)*
Optional.
Overrides the constrainedness score calculated as described in the
dispatch technique. Most scores calculated that way will typically
between 0 and 100. Setting a score manually to something very high
(e.g. 9999) will pretty much guarantee that it gets chosen over other
candidates when multiple signatures match. Setting it to something low
(e.g. -1) will mean it gets avoided.
`no_dispatcher` *(Bool)*
Optional. Defaults to true in roles, false otherwise.
If set to true, Sub::MultiMethods will register the candidate method
but won't install a dispatcher. You should mostly not worry about this
and accept the default.
The @_ passed to Sub::MultiMethod is pre-processed slightly after $name
has been shifted off but before being interpreted as the %spec hash.
Obviously hashes are expected to have string keys, so if the first
argument of @_ is an arrayref or hashref, those are interpreted as a
positional or named signature respectively.
So the following are equivalent:
multimethod foo => [...] => %spec;
multimethod foo => ( positional => [...], %spec );
And so are the following (note that the hashref becomes an arrayref):
multimethod foo => {...} => ( %spec );
multimethod foo => ( named => [...], %spec );
After this, if @_ is an odd-sized list with a coderef in the last
position, the coderef is treated as the `code` option.
So the following are equivalent:
multimethod foo => ( %spec ) => sub { ... };
multimethod foo => ( %spec, code => sub { ... } );
For the common case of:
multimethod foo => (
positional => [...],
code => sub {
...;
},
);
These combination of shortcuts allow it to be written as:
multimethod foo => [...] => sub {
...;
};
`monomethod $name => %spec`
`monomethod($name, %spec)` is basically just a shortcut for
`multimethod(undef, alias => $name, %spec)` though with error messages
which don't mention it being an alias.
`multifunction $name => %spec`
Like `multimethod` but defaults to `method => 0`.
`monofunction $name => %spec`
Like `monomethod` but defaults to `method => 0`.
`VOID`, `SCALAR`, `LIST`, `NONVOID`, `NONSCALAR`, `NONLIST`
Useful constants you can export to allow this to work:
want => NONVOID,
Dispatch Technique
When a multimethod is called, a list of packages to inspect for candidates
is obtained by crawling @ISA. (For multifuncs, @ISA is ignored.)
All candidates for the invoking class and all parent classes are
considered.
If any parent class includes a mono-method (i.e. not a multimethod) of the
same name as this multimethod, then it is considered to have override any
candidates further along the @ISA chain. (With multiple inheritance, this
could get confusing though!) Those further candidates will not be
considered, however the mono-method will be considered to be a candidate,
albeit one with a very low score. (See scoring later.)
Any candidates where it is clear they will not match based on parameter
count will be discarded immediately.
After that, the signatures of each are tried. If they throw an error, that
candidate will be discarded.
If there are still multiple possible candidates, they will be sorted based
on how constrained they are.
To determine how constrained they are, every type constraint in their
signature is assigned a score. Any is 0. Defined inherits from Any, so has
score 1. Value inherits from Defined, so has score 2. Etc. Some types
inherit from a parent but without further constraining the parent. (For
example, Item inherits from Any but doesn't place any additional
constraints on values.) In these cases, the child type has the same score
as its parent. All these scores are added together to get a single score
for the candidate. For candidates where the signature is a coderef, this
is essentially a zero score for the signature unless a score was specified
explicitly.
The score has 100,000 added if `want` or `if` was specified.
If multiple candidates are equally constrained, child class candidates
beat parent class candidates; class candidates beat role candidates; and
the candidate that was declared earlier wins.
Method-resolution order (DFS/C3) is respected, though in Perl 5.8 under
very contrived conditions (calling a sub as a function when it was defined
as a method, but not passing a valid invocant as the first parameter), MRO
may not always work correctly.
Note that invocants are not part of the signature, so not taken into
account when calculating scores, but because child class candidates beat
parent class candidates, they should mostly behave as expected.
After this, there should be one preferred candidate or none. If there is
none, an error occurs. If there is one, that candidate is dispatched to
using `goto` so there is no trace of Sub::MultiMethod in `caller`. It gets
passed the result from checking the signature earlier as @_.
Roles
As far as I'm aware, Sub::MultiMethod is the only multimethod
implementation that allows multimethods imported from roles to integrate
into a class.
use v5.12;
use strict;
use warnings;
package My::RoleA {
use Moo::Role;
use Sub::MultiMethod qw(multimethod);
use Types::Standard -types;
multimethod foo => (
positional => [ HashRef ],
code => sub { return "A" },
alias => "foo_a",
);
}
package My::RoleB {
use Moo::Role;
use Sub::MultiMethod qw(multimethod);
use Types::Standard -types;
multimethod foo => (
positional => [ ArrayRef ],
code => sub { return "B" },
);
}
package My::Class {
use Moo;
use Sub::MultiMethod qw(multimethod);
use Types::Standard -types;
with qw( My::RoleA My::RoleB );
multimethod foo => (
positional => [ HashRef ],
code => sub { return "C" },
);
}
my $obj = My::Class->new;
say $obj->foo_a( {} ); # A (alias defined in RoleA)
say $obj->foo( [] ); # B (candidate from RoleB)
say $obj->foo( {} ); # C (Class overrides candidate from RoleA)
All other things being equal, candidates defined in classes should beat
candidates imported from roles.
CodeRef multimethods
The $name of a multimethod may be a scalarref, in which case `multimethod`
will install the multimethod as a coderef into the scalar referred to.
Example:
my ($coderef, $otherref);
multimethod \$coderef => (
method => 0,
positional => [ ArrayRef ],
code => sub { say "It's an arrayref!" },
);
multimethod \$coderef => (
method => 0,
alias => \$otherref,
positional => [ HashRef ],
code => sub { say "It's a hashref!" },
);
$coderef->( [] );
$coderef->( {} );
$otherref->( {} );
The $coderef and $otherref variables will actually end up as blessed
coderefs so that some tidy ups can take place in `DESTROY`.
Exporter
Sub::MultiMethod uses Exporter::Tiny as an exporter, which means exported
functions can be renamed, etc.
use Sub::MultiMethod multimethod => { -as => 'mm' };
You can import everything using `-all`:
use Sub::MultiMethod -all;
If your Perl version is recent enough, you can import everything as
lexical keywords:
use Sub::MultiMethod -lexical, -all;
You may also set various defaults in the import:
use Sub::MultiMethod multimethod => {
-as => 'mm',
defaults => { bless => 0 }, # see Type::Params
};
API
Sub::MultiMethod avoids cute syntax hacks because those can be added by
third party modules. It provides an API for these modules.
Brief note on terminology: when you define multimethods in a class, each
possible signature+coderef is a "candidate". The method which makes the
decision about which candidate to call is the "dispatcher". Roles will
typically have candidates but no dispatcher. Classes will need dispatchers
setting up for each multimethod.
`Sub::MultiMethod->install_candidate($target, $sub_name, %spec)`
$target is the class (package) name being installed into.
$sub_name is the name of the method.
%spec is the multimethod spec. If $target is a role, you probably want
to include `no_dispatcher => 1` as part of the spec.
`Sub::MultiMethod->install_dispatcher($target, $sub_name, $is_method)`
$target is the class (package) name being installed into.
$sub_name is the name of the method.
$is_method is an integer/boolean.
This rarely needs to be manually called as `install_candidate` will do
it automatically.
`Sub::MultiMethod->install_monomethod($target, $sub_name, %spec)`
Installs a regular (non-multimethod) method into the target.
`Sub::MultiMethod->copy_package_candidates(@sources => $target)`
@sources is the list of packages to copy candidates from.
$target is the class (package) name being installed into.
Sub::MultiMethod will use Role::Hooks to automatically copy candidates
from roles to consuming classes if your role implementation is
supported. (Supported implementations include Role::Tiny, Role::Basic,
Moo::Role, Moose::Role, and Mouse::Role, plus any role implementations
that extend those. If your role implementation is something else, then
when you consume a role into a class you may need to copy the
candidates from the role to the class.)
`Sub::MultiMethod->install_missing_dispatchers($target)`
Should usually be called after `copy_package_candidates`, unless
$target is a role.
Again, this is unnecessary if your role implementation is supported by
Role::Hooks.
`Sub::MultiMethod->get_multimethods($target)`
Returns the names of all multimethods declared for a class or role,
not including any parent classes.
`Sub::MultiMethod->has_multimethod_candidates($target, $method_name)`
Indicates whether the class or role has any candidates for a
multimethod. Does not include parent classes.
`Sub::MultiMethod->get_multimethod_candidates($target, $method_name)`
Returns a list of candidate spec hashrefs for the method, not
including candidates from parent classes.
`Sub::MultiMethod->get_all_multimethod_candidates($target, $method_name,
$is_method)`
Returns a list of candidate spec hashrefs for the method, including
candidates from parent classes (unless $is_method is false, because
non-methods shouldn't be inherited).
`Sub::MultiMethod->known_dispatcher($coderef)`
Returns a boolean indicating whether the coderef is known to be a
multimethod dispatcher.
`Sub::MultiMethod->pick_candidate(\@candidates, \@args, $wantarray)`
Returns a list of two items: first the winning candidate from an array
of specs, given the args and invocants, and second the modified args
after coercion has been applied. $wantarray should be a string 'VOID',
'SCALAR', or 'LIST'.
This is basically how the dispatcher for a method works:
my $pkg = __PACKAGE__;
if ( $ismethod ) {
$pkg = Scalar::Util::blessed( $_[0] ) || $_[0];
}
my ( $winner, $new_args ) = 'Sub::MultiMethod'->pick_candidate(
[
'Sub::MultiMethod'->get_all_multimethod_candidates(
$pkg,
$sub,
$ismethod,
)
],
\@_,
wantarray ? 'LIST' : defined(wantarray) ? 'SCALAR' : 'VOID',
);
$winner->{code}->( @$new_args );
`Sub::MultiMethod->clear_cache`
The `dispatch` method caches what `get_all_multimethod_candidates`
returns. It is expected that by the time a multisub/multimethod is
called, you have finished adding new candidates, so this should not be
harmful. If you do add new candidates, then the cache should
automatically clear itself anyway. However if new candidates emerge
by, for example, altering a class's @ISA at run time, you may need to
manually clear the cache. This is a very unlikely situation though.
`Sub::MultiMethod->get_cache`
Gets a reference to the dispatch cache hash. Mostly for people wanting
to subclass Sub::MultiMethod, especially if you want to override the
`dispatch` method.
EXAMPLES
Naive (Slightly Broken) JSON Writer
Here is similar code to the "SYNOPSIS", but written as a function instead
of a method, employing a few shortcuts, and with a variant that throws an
error if called in void context.
use v5.20;
use strict;
use warnings;
use experimental 'signatures';
package My::JSON {
use Sub::MultiMethod multifunction => { -as => 'multi' };
use Types::Standard -types;
multi stringify => ( want => 'VOID', die => 'Unexpected void context' );
multi stringify => [ Undef ] => ( return => 'null' );
multi stringify => [ ScalarRef[Bool] ] => sub ( $bool ) {
return $$bool ? 'true' : 'false';
};
multi stringify => [ Str ] => (
alias => "stringify_str",
code => sub ( $str ) {
return sprintf( q<"%s">, quotemeta($str) );
},
);
multi stringify => [ Num ] => sub ( $n ) {
return $n;
};
multi stringify => [ ArrayRef ] => sub ( $arr ) {
return sprintf(
q<[%s]>,
join( q<,>, map( stringify($_), @$arr ) )
);
};
multi stringify => [ HashRef ] => sub ( $hash ) {
return sprintf(
q<{%s}>,
join(
q<,>,
map sprintf(
q<%s:%s>,
stringify_str($_),
stringify( $hash->{$_} )
), sort keys %$hash,
)
);
};
}
say My::JSON::stringify( {
foo => 123,
bar => [ 1, 2, 3 ],
baz => \1,
quux => { xyzzy => 666 },
} );
BUGS
Please report any bugs to
<http://rt.cpan.org/Dist/Display.html?Queue=Sub-MultiMethod>.
SEE ALSO
Multi::Dispatch - probably almost as nice an implementation as
Sub::MultiMethod. It correctly handles inheritance, does a good job of
dispatching to the best candidate, etc. It's even significantly faster
than Sub::MultiMethod. On the downsides, it doesn't handle roles or
coercions.
Class::Multimethods - uses Perl classes and ref types to dispatch. No
syntax hacks but the fairly nice syntax shown in the pod relies on `use
strict` being switched off! Need to quote a few more things otherwise.
Class::Multimethods::Pure - similar to Class::Multimethods but with a more
complex type system and a more complex dispatch method.
Logic - a full declarative programming framework. Overkill if all you want
is multimethods. Uses source filters.
Dios - object oriented programming framework including multimethods.
Includes a full type system and Keyword::Declare-based syntax. Pretty
sensible dispatch technique which is almost identical to Sub::MultiMethod.
Much much slower though, at both compile time and runtime.
MooseX::MultiMethods - uses Moose type system and Devel::Declare-based
syntax. Not entirely sure what the dispatching method is.
Kavorka - I wrote this, so I'm allowed to be critical. Type::Tiny-based
type system. Very naive dispatching; just dispatches to the first declared
candidate that can handle it rather than trying to find the "best".
Sub::Multi::Tiny - uses Perl attributes to declare candidates to be
dispatched to. Pluggable dispatching, but by default uses argument count.
Sub::Multi - syntax wrapper around Class::Multimethods::Pure?
Sub::SmartMatch - kind of abandoned and smartmatch is generally seen as
teh evilz these days.
AUTHOR
Toby Inkster <tobyink@cpan.org>.
COPYRIGHT AND LICENCE
This software is copyright (c) 2020-2022 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under the
same terms as the Perl 5 programming language system itself.
DISCLAIMER OF WARRANTIES
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.