NAME
Dispatch::Fu - Converts any complicated conditional dispatch situation into familiar static hash-key based dispatch
SYNOPSIS
use strict;
use warnings;
use Dispatch::Fu qw/dispatch on/; # exported by default, just for show here
my $INPUT = [qw/1 2 3 4 5/];
my $results = dispatch { # <~ start of 'dispatch' construct
my $input_ref = shift; # <~ input reference
return ( scalar @$input_ref > 5 ) # <~ return a string that must be
? q{case5} # defined below using the 'on'
: sprintf qq{case%d}, scalar @$input_ref; # keyword, this i
} $INPUT, # <~ input reference, SCALAR passed to dispatch BLOCK
on case0 => sub { print qq{case 0\n}; 0}, # <~ if dispatch returns 'case0', run this CODE
on case1 => sub { print qq{case 1\n}; 1} , # <~ if dispatch returns 'case1', run this CODE
on case2 => sub { print qq{case 2\n}; 2}, # ... ... ... ... ... ... ...
on case3 => sub { print qq{case 3\n}; 3}, # ... ... ... ... ... ... ... ...
on case4 => sub { print qq{case 4\n}; 4}, # ... ... ... ... ... ... ...
on case5 => sub { print qq{case 5\n}; 5}; # <~ if dispatch returns 'case5', run this CODE
DESCRIPTION
Dispatch::Fu
provides an idiomatic and succinct way to organize a HASH
-based dispatch table by first computing a static key using a developer defined process. This static key is then used immediate to execute the subroutine reference registered to the key.
This module presents a generic structure that can be used to implement all of the past attemts to bring things to Perl like, switch or case statements, given/when, smartmatch, etc.
The Problem
HASH
based dispatching in Perl is a very fast and well established way to organize your code. A dispatch table can be fashioned easily when the dispatch may occur on a single variable that may be one or more static strings suitable to serve also as HASH
a key.
For example, the following is more or less a classical example of this approach that is fundamentally based on a 1:1 mapping of a value of $action
to a HASH
key defined in $dispatch
:
my $CASE = get_case(); # presumed to return one of the hash keys used below
my $dispatch = {
do_dis => sub { ... },
do_dat => sub { ... },
do_deez => sub { ... },
do_doze => sub { ... },
};
if (not $CASE or not exists $dispatch->{$CASE}) {
die qq{case not supported\n};
}
my $results = $dispatch->{$CASE}->();
But this nice situation breaks down if $CASE
is a value that is not suitable for us as a HASH
key, is a range of values, or a single variable (e.g., $CASE
) is not sufficient to determine what case to dispatch. Dispatch::Fu
solves this problem by providing a stage where a static key might be computed or classified.
The Solution
Dispatch::Fu
solves the problem by providing a Perlish and idiomatic hook for computing a static key from an arbitrarily defined algorithm written by the developer using this module.
The dispatch
keyword and associated lexical block (that should be treated as the body of a subroutine that receives exactly one parameter), determines what case defined by the on
keyword is immediately executed.
The simple case above can be trivially replicated below using Dispatch::Fu
, as follows:
my $results = dispatch {
my $case = shift;
return $case;
},
$CASE,
on do_dis => sub { ... },
on do_dat => sub { ... },
on do_deez => sub { ... },
on do_doze => sub { ... };
The one difference here is, if $case
is defined but not accounted for using the on
keyword, then dispatch
will throw an exception via die
. Certainly any logic meant to deal with the value (or lack thereof) of $CASE
should be handled in the dispatch
BLOCK.
An example of a more complicated scenario for generating the static key might be defined, follows:
my $results = dispatch {
my $input_ref = shift;
my $rand = $input_ref->[0];
if ( $rand < 2.5 ) {
return q{do_dis};
}
elsif ( $rand >= 2.5 and $rand < 5.0 ) {
return q{do_dat};
}
elsif ( $rand >= 5.0 and $rand < 7.5 ) {
return q{do_deez};
}
elsif ( $rand >= 7.5 ) {
return q{do_doze};
}
},
[ rand 10 ],
on do_dis => sub { ... },
on do_dat => sub { ... },
on do_deez => sub { ... },
on do_doze => sub { ... };
The approach facilited by Dispatch::Fu
is one that requires the programmer to define each case by a static key via on
, and define a custom algorithm for picking which case (by way of return
'ing the correct static key as a string) to execute using the dispatch
BLOCK.
USAGE
For more working examples, look at the tests in the ./t
directory. It should quickly become apparent how to use this method and what it's for by trying it out. But if in doubt, please inquire here, there, everywhere.
dispatch
BLOCK-
BLOCK
is required, and is coerced to be an anonymous subroutine that is passed a single scalar reference; this reference can be a single value or point to anything a Perl scalar reference can point to. It's the single point of entry for input.my $results = dispatch { my $input_ref = shift; # <~ there is only one parameter, but can a reference to anything my $key = q{default}; # <~ initiate the default key to use, 'default' by convention not required ... # <~ compute $key (yada yada) return $key; # <~ key must be limited to the set of keys added with C<on> } ...
The
dispatch
implementation must return a static string, and that string should be one of the keys added using theon
keyword. Otherwise, an exception will be thrown viadie
. cases
-
This routine is for introspection inside of the
dispatch
BLOCK. It returns the list of all cases added by theon
routine. Outside of thedispatch
BLOCK, it returns an emptyHASH
reference.Note: do not rely on the ordering of these cases to be consistent; it relies on the
keys
keyword, which operates onHASH
es and key order is therefore not deterministic.Given the full example above,
my $results = dispatch { my $input_ref = shift; ... my @cases = cases; # (qw/do_dis do_dat do_deez do_doze/) ... }, [ rand 10 ], on do_dis => sub { ... }, on do_dat => sub { ... }, on do_deez => sub { ... }, on do_doze => sub { ... };
REF
-
This is the singular scalar reference that contains all the stuff to be used in the
dispatch
BLOCK. In the example above it is,[rand 10]
. It is the way to pass arbitrary data intodispatch
. E.g.,my $INPUT = [qw/foo bar baz 1 3 4 5/]; my $result = dispatch { my $input_ref = shift; # <~ there is only one parameter, but can a reference to anything my $key = q{default}; # <~ initiate the default key to use, 'default' by convention not required ... # <~ compute $key (yada yada) return $key; # <~ key must be limited to the set of keys added with C<on> } $INPUT, ### <><~ the single scalar reference to be passed to the C<dispatch> BLOCK ...
on
-
This keyword builds up the dispatch table. It consists of a static string and a subroutine reference. In order for this to work for you, the
dispatch
BLOCK must return strictly only the keys that are defined viaon
.my $INPUT = [qw/foo bar baz 1 3 4 5/]; my $results = dispatch { my $input_ref = shift; # <~ there is only one parameter, but can a reference to anything my $key = q{default}; # <~ initiate the default key to use, 'default' by convention not required ... # <~ compute $key (yada yada) return $key; # <~ key must be limited to the set of keys added with C<on> } $INPUT, ### <><~ the single scalar reference to be passed to the C<dispatch> BLOCK on case1 => sub { ... }, on case2 => sub { ... }, on case3 => sub { ... }, on case4 => sub { ... }, on case5 => sub { ... };
Note: It was made as a design decision that there be no way to specify the input parameters into the subroutine reference that is added by each
on
statement. This means that the subroutine refs are to be treated as wrappers that access the current scope. This provides maximum flexibility and allows one to manage what happens in eachon
case more explicitly. Perl's scoping rules lets one use variables in a higher scope, so that would be the way to deal with it. E.g.,my $INPUT = [qw/foo bar baz 1 3 4 5/]; my $results = dispatch { my $input_ref = shift; # there is only one parameter, but can a reference to anything my $key = q{default}; # initiate the default key to use, 'default' by convention not required ... # compute $key return $key; # key must be limited to the set of keys added with C<on> } $INPUT, # <~ the single scalar reference to be passed to the C<dispatch> BLOCK on q{default} => sub { do_default($INPUT) }, on q{key1} => sub { do_key1(cases => $INPUT) }, on q{key2} => sub { do_key2(qw/some other inputs entirely/) };
BUGS
Please report any bugs or ideas about making this module an even better basis for doing dynamic dispatching.
AUTHOR
O. ODLER 558 <oodler@cpan.org>.
LICENSE AND COPYRIGHT
Same as Perl.