NAME
Params::Signature::Multi - support for subroutine selection and dispatch based on subroutine signature and parameters
VERSION
Version 0.05
SYNOPSIS
use Params::Signature::Multi;
use MyClass;
my $prototypes = {
sub_one_a => ["Int one", "Int two"],
sub_one_b => ["Int one", "Str two"],
sub_one_fallback => ["..."],
method_two_a => ["MyClass self", "Int one", "Int two"],
method_two_b => ["MyClass self", "Int one", "Str two"]
method_two_fallback => ["MyClass self", "..."]
}:
my $multi = new Params::Signature::Multi();
# register custom class
$multi->validator->register_class("MyClass");
# call "public" subroutine
sub_one(1,"hi")
sub sub_one
{
return $multi->dispatch(\@_,
[
{ signature => $prototypes{sub_one_a}, call => \&_sub_one_a},
{ signature => $prototypes{sub_one_b}, call => \&_sub_one_a}
{ signature => $prototypes{sub_one_fallback}, call => \&_sub_one_fallback}
]);
}
sub _sub_one_a
{
my $params = $multi->validator->validate(\@_, $prototypes{sub_one_a});
...
}
sub _sub_one_b
{
my $params = $multi->validator->validate(\@_, $prototypes{sub_one_b});
...
}
sub _sub_one_fallback
{
# perhaps return a default value or print an error
...
}
$obj = new MyClass();
# call public method
$obj->method_two(1,"hi");
sub method_two
{
return $multi->dispatch(\@_,
[
{ signature => $prototypes{method_two_a}, call => \&_method_two_a},
{ signature => $prototypes{method_two_b}, call => \&_method_two_b}
{ signature => $prototypes{method_two_fallback}, call => \&_method_two_fallback}
]);
}
sub _method_two_a
{
my $params = $multi->validator->validate(\@_, $prototypes{method_two_a});
...
}
sub _method_two_b
{
my $params = $multi->validator->validate(\@_, $prototypes{method_two_b});
...
}
sub _method_two_fallback
{
...
}
# determine which signature matches and handle the rest locally
sub my_accessor
{
my $self = shift;
my ($idx, $id) = $multi->resolve(
params => \@_,
signatures => [
{ id => "set", signature => ["Str key", "Str value"]},
{ id => "get", signature => ["Str key"], }
# without a fallback, Carp::confess is called when nothing matches
{ id => "fallback", signature => ["..."], }
],
param_tyle => "positional"
);
if ($id eq "set")
{
return $self->{$_[0]} = $_[1];
}
elsif ($id eq "get")
{
return $self->{$_[0]};
}
else # $id eq fallback
{
shout("You didn't give me a key!");
}
}
DESCRIPTION
Many object-oriented languages, including Perl 6, allow you to define multiple methods with the same name but a different subroutine signature. At run time, the interpreter determines which signature matches the parameters being passed to the method and calls the corresponding method. This module does not provide a way to define multiple subroutines with the same name nor does it define new keywords. Instead, one subroutine serves as the public interface to a number of "private" subroutines.
This module works like a dispatch table which is given a list of "private" subroutines and their signatures. Params::Signature::Multi uses Params::Signature to compare signatures with parameters and select the first matching signature. A private subroutine is called when its signature matches the parameters passed to the public subroutine. Each private subroutine exists either as a regular subroutine (with its own name) or an anonymous subroutine.
In its simplest form, you simply call Params::Signature::Multi's "dispatch" method with the parameters and a list of signature specifications, each of which includes a reference to a subroutine:
return $multi->dispatch(\@_,
[
{ signature => ["Int one", "Str two = 'A default value'", "Undef|Str three?], call => \&call_me},
{ signature => ["Int one"], call => \&or_call_me}
{ signature => ["..."], call => \&call_me_fallback}
],
# default parameter style is 'positional' (or whatever was passed to
# '$multi' object's constructor) ...
# override default and force parameter style to 'named'
"named",
\&my_error_handler
);
return $multi->dispatch(
params => \@_,
signatures => [
{ signature => ["Int one", "Str two = 'A default value'", "Undef|Str three?], call => \&call_me},
{ signature => ["Int one"], call => \&or_call_me}
{ signature => ["..."], call => \&call_me_fallback}
],
param_style => "named",
on_fail => \&my_error_handler
);
A signature is a list of parameter definitions as defined in Params::Signature. Note that default values defined in the signature are ignored, and therefore not set, by "dispatch" or "resolve". These methods compare parameters with parameter type constraints only.
The list of signatures is defined in the same order used by "dispatch" and "resolve" to evaluate signatures and identify the first matching signature. The most specific signature should be first with more general signatures following it. The first signature that matches is considered the "best match", even if subsequent signatures would also match. A "fallback" signature which accepts anything (via the "..." pseudo-parameter) can be used to handle situations where the parameters don't match any signature.
The "resolve" method finds the best matching signature, but does not call a private subroutine. Instead, it simply determines which signature matches a subroutine's parameters and returns it's identifier. The subroutine can then use the identifier to respond accordingly. Using "resolve" makes sense for subroutines like object getter/setter methods which handle everything locally.
METHODS
All functionality is implemented via object (or class) methods. You can use "dispatch" and "resolve" as Params::Signature::Multi class methods rather than constructing a module or application-specific Params::Signature::Multi object.
new
The "new" method takes the same parameters as those passed to Params::Signature's "new" method.
new Params::Signature::Multi(
param_style => "positional",
fuzzy => 1,
on_fail => sub { oops($_[0]) },
normalize_keys => sub { lc $_[0] },
called => "My::Module"
);
resolve
Only resolve which signature actually matches the parameters passed to a subroutine.
params: A reference to an array of parameters passed to the calling subroutine, typically \@_. This array is left alone and may be used after the call to resolve.
signatures: A list of signatures in the order in which resolve will attempt to find a matching signature. If signatures are similar, more specific signatures (those that have more parameters or stricter type constraints) should appear first, with the least specific signature at the end. See the "LIMITATIONS AND CAVEATS" section below for additional considerations.
signature:
The actual subroutine signature.
id:
Optional ID assigned to a signature.
param_style: Explicitly set the parameter style used to evaluate parameters.
on_fail: Override Carp::confess as the subroutine that gets called when a failure occurs. This parameter must be passed to resolve as a named parameter.
Return Value:
In scalar context, the method returns the index of the signature that was selected. If the optional "id" value is set in the signatures section, the "id" is returned instead of the index. In list context, the method returns the index and id.
my ($idx, $id) = $multi->resolve(
params => \@_,
signatures => [
{ id => "set", signature => ["Str key", "Str value"]},
{ id => "get", signature => ["Str key"], }
],
param_tyle => "positional"
);
dispatch
Determine which signature matches the parameters in params and call the corresponding subroutine.
params: A reference to an array of parameters passed to the calling subroutine, typically \@_. This array is left alone and may be used after the call to dispatch.
signatures: A list of signatures in the order in which resolve will attempt to find a matching signature. If signatures are similar, more specific signatures (those that have more parameters) should appear first, with the least specific signature at the end.
signature:
The actual subroutine signature.
call:
The subroutine to call if the
signaturematches. The list assigned toparamsis passed to the subroutine as@_.
param_style: Explicitly set the parameter style used to evaluate parameters.
on_fail: Override Carp::confess as the subroutine that gets called when a failure occurs. This parameter must be passed to dispatch as a named parameter.
Return Value:
The return value of the subroutine that was called or undef if nothing was called.
return $multi->dispatch(
params => \@_,
signatures => [
{ signature => ["Int one", "Str two = 'A default value'", "Undef|Str three?], call => \&_call_me},
{ signature => ["Int one"], call => \&_or_call_me}
],
param_tyle => "positional"
);
validator
Either get or set the Params::Signature object used to evaluate signatures.
Return Value:
The current Params::Signature object used to examine signatures.
my $multi = new Params::Signature::Multi(param_style => "named");
$multi->validator(new Params::Signature(fuzzy => 1, param_style => "named"));
$multi->validator->register_class("MyClass");
$params = $multi->validator->validate(\@_, ["Str name", "Int id"]);
validate
A convenience method to use the validator object to validate the parameters passed to a subroutine. See the Params::Signature module for more information concerning the validate method.
Return Value:
In list context, validated parameters are returned as a list. In scalar context, the method returns a reference to a hash containing parameters which have a value. The parameter name is the key.
my $multi = new Params::Signature::Multi(param_style => "named");
$params = $multi->validate(\@_, ["Str name", "Int id"]);
@params = $multi->validate(\@_, ["Str name", "Int id"]);
PERFORMANCE
A Params::Signature object is used to cache the parsed form of each signature evaluated. Re-using the same object to evaluate subroutine parameters eliminates the need to parse the signature every time. Using a Params::Signature::Multi singleton per module or application is recommended for reducing the amount of time it takes to evaluate parameters.
Using a "prototypes" hash to store signatures make it easier to define a signature once and then re-use the same signature throughout a module or application. This is not a requirement, but may make maintenance a lot easier. This also means your signature is defined only once rather than on every call. You can technically use anything to store a reference to the signature, but a hash makes it easy to find a signature. Of course, that's just an opinion.
LIMITATIONS AND CAVEATS
Resolution Method
This is a "poor man's" method for supporting multi methods and subroutines. The current means of selecting the first signature that matches rather than evaluating all of them and picking the "best match" may not be the best approach. Evaluating signatures in list order has the advantage of giving the programmer some control over the order of evaluation, which should produce predictable results. This remains a "best effort" and is not intended to emulate more sophisticated systems.
Inheritance
Inheritance is not supported directly. The initial ("public") method that gets called is resolved by perl. Once the method is called, other methods in the public method's package are available as values for use with "dispatch"'s call parameter. If you only pass references to "private" methods in as values for call, you may not need to worry about a child class overriding a method. If you call a "public" method, there is no easy way to know if that method has been overriden in a child object. If you really need proper inheritance and multi-method handling, you may need Moose with MooseX and not this package.
A possible solution to inheritance is to wrap the call in an anonymous subroutine which then relies on perl to resolve the method being called:
my $self = shift;
return $multi->dispatch(
params => \@_,
signatures => [
# instead of this:
# { signature => ["Int one", "Str two", call => \&call_me},
# do this:
{ signature => ["Int one", "Str two", call => sub { $self->call_me(@_) },
# not this ...
# { signature => ["Int one"], call => \&or_call_me}
# try this ...
{ signature => ["Int one"], call => sub { $self->or_call_me(@_) }
],
param_tyle => "positional"
);
This method relies on $self being re-evaluated each time the anonymous subroutine gets assigned as the call parameter. This also requires that you shift off $self from @_. Give it a try. Your mileage may may vary.
Type Constraints
Keep in mind that some type constraints are somewhat interchangable because of the way perl itself handles data. For example, an integer value stored in a perl scalar will automatically behave like a string in the right context. A string that contains an integer value can be treated like an integer. In perl, there is no way to distinguish between one type of value versus the other. The type constraints perform tests to see if a value has certain characteristics. For example, the test for an Int is more specific (value must contain only digits) than the type constraint tests for a Str. If the only difference between two signatures is an Int versus an Str parameter, place the signature with the Int constraint before the signature with the Str value. The stricter test for an Int will fail for anything that is not an integer value, but the test for a Str will accept a numeric value as well.
sub int_or_str
{
my ($idx, $id) = $multi->resolve(
params => \@_,
signatures => [
{ id => "is_int", signature => ["Int arg_one"]},
{ id => "is_str", signature => ["Str arg_one"]},
],
param_tyle => "positional"
);
...
}
int_or_str(123); # resolves to "is_int"
int_or_str("123"); # also resolves to "is_int"!
int_or_str("hi"); # resolves to "is_str"
Using named, rather than positional, parameters can help eliminate some ambiguity, provided you use a different parameter name for the different types of values. If, like in the example above, both signatures use the same name (arg_one), using named parameters doesn't have any advantage over positional parameters. Changing the names to int_arg and str_arg and using named parameters clears things up:
sub int_or_str
{
my ($idx, $id) = $multi->resolve(
params => \@_,
signatures => [
{ id => "is_int", signature => ["Int int_arg"]},
{ id => "is_str", signature => ["Str str_arg"]},
],
param_tyle => "named"
);
...
}
int_or_str(int_arg => 123); # resolves to "is_int"
int_or_str(int_arg => "123"); # also resolves to "is_int"!
int_or_str(str_arg => "hi"); # resolves to "is_str"
int_or_str(str_arg => "123"); # also resolves to "is_str"!
AUTHOR
Sandor Patocs
BUGS
Please report any bugs or feature requests on GitHub at https://github.com/spatocs/params_signature/issues.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Params::Signature::Multi
You can also look for information at:
GitHub: source repository and issue tracker
MetaCPAN
ACKNOWLEDGEMENTS
SEE ALSO
MooseX::Method::Signatures, MooseX::MultiMethods, Method::Signatures, Perl6::Signature
LICENSE AND COPYRIGHT
Copyright 2013 Sandor Patocs.
This program is distributed under the terms of the Artisitic License (2.0)