File Coverage

blib/lib/Sub/Multi/Tiny.pm
Criterion Covered Total %
statement 185 188 98.4
branch 32 52 61.5
condition 7 15 46.6
subroutine 36 38 94.7
pod n/a
total 260 293 88.7


line stmt bran cond sub pod time code
1              
2             use 5.006;
3 13     13   100274 use strict;
  13         72  
4 13     13   65 use warnings;
  13         61  
  13         274  
5 13     13   405  
  13         54  
  13         536  
6             require Attribute::Handlers; # Listed here so automated tools see it
7              
8             use Import::Into;
9 13     13   2338 use Scalar::Util qw(looks_like_number);
  14         13994  
  14         393  
10 14     13   78 use Sub::Multi::Tiny::SigParse;
  14         38  
  14         905  
11 14     13   5418 use Sub::Multi::Tiny::Util ':all';
  14         134  
  14         510  
12 14     13   2241 use subs ();
  14         38  
  14         1876  
13 14     13   6092 use vars ();
  14         305  
  14         302  
14 14     13   71  
  13         31  
  13         458  
15             our $VERSION = '0.000013';
16              
17             use constant { true => !!1, false => !!0 };
18 13     12   78  
  13         52  
  13         2583  
19             # Documentation {{{1
20              
21             =encoding utf-8
22              
23             =head1 NAME
24              
25             Sub::Multi::Tiny - Multisubs/multimethods (multiple dispatch) yet another way!
26              
27             =head1 SYNOPSIS
28              
29             {
30             package main::my_multi; # We're making main::my_multi()
31             use Sub::Multi::Tiny qw($foo $bar); # All possible params
32              
33             sub first :M($foo, $bar) { # sub's name will be ignored,
34             return $foo ** $bar; # but can't match the one we're making
35             }
36              
37             sub second :M($foo) {
38             return $foo + 42;
39             }
40              
41             }
42              
43             # Back in package main, my_multi() is created just before the run phase.
44             say my_multi(2, 5); # -> 32
45             say my_multi(1295); # -> 1337
46              
47             The default dispatcher dispatches solely by arity, and only one
48             candidate can have each arity. For more flexible dispatching, see
49             L<Sub::Multi::Tiny::Dispatcher::TypeParams>.
50              
51             =head1 DESCRIPTION
52              
53             Sub::Multi::Tiny is a library for making multisubs, aka multimethods,
54             aka multiple-dispatch subroutines. Each multisub is defined in a
55             single package. Within that package, the individual implementations ("impls")
56             are C<sub>s tagged with the C<:M> attribute. The names of the impls are
57             preserved but not used specifically by Sub::Multi::Tiny.
58              
59             Within a multisub package, the name of the sub being defined is available
60             for recursion. For example (using C<where>, supported by
61             L<Sub::Multi::Tiny::Dispatcher::TypeParams>):
62              
63             {
64             package main::fib;
65             use Sub::Multi::Tiny qw(D:TypeParams $n);
66             sub base :M($n where { $_ <= 1 }) { 1 }
67             sub other :M($n) { $n * fib($n-1) }
68             }
69              
70             This code creates function C<fib()> in package C<main>. Within package
71             C<main::fib>, function C<fib()> is an alias for C<main::fib()>. It's easier
72             to use than to explain!
73              
74             =head1 FUNCTIONS
75              
76             =cut
77              
78             # }}}1
79              
80             # Information about the multisubs so we can create the dispatchers at
81             # INIT time.
82             my %_multisubs;
83              
84             # Sanity check: any :M will die after the INIT block below runs.
85             my $_dispatchers_created;
86              
87             # Accessor
88              
89 30     28   168 # INIT: Fill in the dispatchers for any multisubs we've created.
90             # Note: attributes are applied at CHECK time, before this.
91             # We use INIT so that compilation failures will prevent this code from running.
92              
93             INIT {
94             _hlog { __PACKAGE__, "in INIT block" };
95             $_dispatchers_created = 1;
96 12     11   838 while(my ($multisub_fullname, $hr) = each(%_multisubs)) {
  8         66  
97 12         122 my $dispatcher = _make_dispatcher($hr)
98 12         530 or die "Could not create dispatcher for $multisub_fullname\()";
99 14 50       55  
100             eval { no strict 'refs'; *{$multisub_fullname} = $dispatcher };
101             die "Could not assign dispatcher for $multisub_fullname\:\n$@" if $@;
102 13     12   457 do {
  13         45  
  13         899  
  11         28  
  11         24  
  12         234  
103 12 50       48 no strict 'refs';
104 12         49 no warnings 'redefine';
105 13     12   79 my $target_name = "$hr->{defined_in}\::$hr->{subname}";
  16         517  
  16         455  
106 16     12   91 *{$target_name} = $dispatcher;
  16         65  
  16         7215  
107 12         61 };
108 12         26 } #foreach multisub
  12         136  
109             } #CHECK
110              
111             =head2 import
112              
113             Sets up the package that uses it to define a multisub. The parameters
114             are all the parameter variables that the multisubs will use. C<import>
115             creates these as package variables so that they can be used unqualified
116             in the multisub implementations.
117              
118             A parameter C<D:Dispatcher> can also be given to specify the dispatcher to
119             use --- see L</CUSTOM DISPATCH>.
120              
121             Also sets L<Sub::Multi::Tiny::Util/$VERBOSE> if the environment variable
122             C<SUB_MULTI_TINY_VERBOSE> has a truthy value. If the C<SUB_MULTI_TINY_VERBOSE>
123             value is numeric, C<$VERBOSE> is set to that value; otherwise, C<$VERBOSE> is
124             set to 1.
125              
126             =cut
127              
128             my $multi_package = caller; # The package that defines the multisub
129             my $my_package = shift; # The package we are
130              
131 17     15   8102 for($ENV{SUB_MULTI_TINY_VERBOSE}) {
132 17         77 last unless $_;
133             $VERBOSE = looks_like_number($_) ? 0+ $_ : 1;
134 17         95 }
135 17 100       77  
136 2 0       7 if(@_ && $_[0] eq ':nop') {
137             _hlog { __PACKAGE__ . ':nop => Taking no action' } 0; # Always
138             return;
139 17 100 66     151 }
140 3     0   10  
  2         8  
141 1         16 _hlog { "Target $multi_package package $my_package" };
142             my ($target_package, $subname) = ($multi_package =~ m{^(.+?)::([^:]+)$});
143             # $target_package is the package that will be able to call the multisub
144 14     10   97 _croak "Can't parse package name ${multi_package} into <target>::<name>"
  10         47  
145 14         187 unless $target_package && $subname;
146              
147 14 50 33     101 _croak "Can't redefine multi sub $multi_package\()"
148             if exists $_multisubs{$multi_package};
149              
150             # Create the vars - they will be accessed as package variables.
151 14 50       46 # TODO: parameters of the form D:<foo> import dispatcher <foo>.
152             my @possible_params;
153             my $dispatcher = 'Default';
154             foreach (@_) {
155 14         28 if(/^D:(.*)$/) {
156 14         25 die '"D:" must be followed by a dispatcher class' unless $1;
157 14         35 $dispatcher=$1;
158 26 100       138 } elsif(/^.:/) {
    50          
159 7 50       53 die '".:..." forms reserved - did you mean "D:DispatcherClass"?';
160 7         31 } else {
161             push @possible_params, $_;
162 0         0 }
163             }
164 19         47  
165             # Load the parameter variables as package variables
166             _croak "Please list the sub parameters" unless @possible_params;
167             vars->import::into($multi_package, @possible_params);
168              
169 14 50       61 # Load the dispatcher
170 14         182 $dispatcher = __PACKAGE__ . "::Dispatcher::$dispatcher"
171             unless index($dispatcher, '::') != -1;
172              
173 14 50       3468 eval "require $dispatcher";
174             die "Could not load dispatcher $dispatcher, requested by $multi_package: $@"
175             if $@;
176 14         700  
177 14 50       86 _hlog { $multi_package, 'using dispatcher', $dispatcher };
178             ${dispatcher}->import::into($multi_package);
179              
180 14     10   109 # Make a stub that we will redefine later
  10         46  
181 14         194 _hlog { "Making $multi_package\()" } ;
182             subs->import::into($target_package, $subname);
183             # TODO add stub for callsame/nextwith/...
184 14     10   2782  
  10         46  
185 14         156 # Save the patch
186             $_multisubs{$multi_package} = {
187             used_by => $target_package,
188             defined_in => $multi_package,
189             subname => $subname,
190             possible_params => +{ map { ($_ => 1) } @possible_params },
191             impls => [], # Implementations - subs tagged :M
192             };
193 14         2773  
  19         124  
194             # Set up the :M attribute in $multi_package if it doesn't
195             # exist yet.
196             if(eval { no strict 'refs'; defined &{$multi_package . '::M'} }) {
197             die "Cannot redefine M in $multi_package";
198             } else {
199 16 50   12   123 _hlog { "Making $multi_package attr M" } 2;
  16         57  
  16         1364  
  14         44  
  14         94  
  14         101  
200 0         0 eval(_make_M($multi_package));
201             die $@ if $@;
202 14     10   96 }
  10         42  
203 14         105  
204 14 50       4431 # Set up $subname() in $multi_package, which will be aliased to the
205             # dispatcher.
206             if(eval { no strict 'refs'; defined &{"$multi_package\::$subname"} }) {
207             die "Cannot redefine $subname in $multi_package";
208             } else {
209 16 50   12   119 _hlog { "Making $multi_package\::$subname stub" } 2;
  16         32  
  16         1189  
  14         34  
  14         24  
  14         104  
210 0         0 do { no strict 'refs'; *{"$multi_package\::$subname"} = sub {} };
211             }
212 14     10   114 } #import()
  10         49  
213 16     12   104  
  16     0   45  
  16         9477  
  14         80  
  14         62  
  14         656  
214             # Parse the argument list to the attribute handler
215             my ($spec, $funcname) = @_;
216             _croak "Need a parameter spec for $funcname" unless $spec;
217             _hlog { "Parsing args for $funcname: $spec" } 2;
218              
219 28     28   71 return Sub::Multi::Tiny::SigParse::Parse($spec);
220 28 50       78 } #_parse_arglist
221 28     22   171  
  22         87  
222             # Create the source for the M attribute handler for a given package
223 28         189 my $multi_package = shift;
224             my $P = __PACKAGE__;
225             my $code = _line_mark_string
226             "package $multi_package;\n";
227              
228 14     14   34 # TODO See if making M an :ATTR(..., BEGIN) permits us to remove the
229 14         31 # requirement to list all the parameters in the `use S::M::T` line
230 14         88  
231             $code .= _line_mark_string <<'EOT';
232             use Attribute::Handlers;
233             use Sub::Multi::Tiny::Util qw(_hlog);
234             ##use Data::Dumper;
235              
236 14         55 sub M :ATTR(CODE,RAWDATA) {
237 11     11   77 _hlog { require Data::Dumper;
  11         25  
  11         81  
238 11     11   444 'In ', __PACKAGE__, "::M: \n",
  11         31  
  11         4035  
239             Data::Dumper->Dump([\@_], ['attr_args']) } 2;
240              
241             my ($package, $symbol, $referent, $attr, $data, $phase,
242 14         78 $filename, $linenum) = @_;
243 14         101 my $funcname = "$package\::" . *{$symbol}{NAME};
244 20         134601  
245             _hlog { # Code from Attribute::Handlers, license perl_5
246 20         139 ref($referent),
247             $funcname,
248 20         57 "($referent)", "was just declared",
  20         83  
249             "and ascribed the ${attr} attribute",
250             "with data ($data)",
251 14         108 "in phase $phase",
252             "in file $filename at line $linenum"
253             } 2;
254             EOT
255              
256             # Trap out-of-sequence calls. Currently you can't create a new multisub
257             # via eval at runtime. TODO use UNITCHECK instead to permit doing so?
258 20         212 $code .= _line_mark_string <<EOT;
259             die 'Dispatchers already created - please file a bug report'
260             if $P\::_dispatchers_created();
261              
262             my \$multi_def = \$_multisubs{'$multi_package'};
263 14         67 EOT
264 20 50       140  
265             # Parse and validate the args
266             $code .= _line_mark_string <<EOT;
267 20         46 my \$hrSig = $P\::_parse_arglist(\$data, \$funcname);
268             $P\::_check_and_inflate_sig(\$hrSig, \$multi_def,
269             \$funcname, \$package, \$filename, \$linenum);
270             EOT
271 14         77  
272 20         58 $code .= _line_mark_string <<'EOT';
273 20         80 EOT
274              
275             # Save the implementation's info for use when making the dispatcher.
276             $code .= _line_mark_string <<'EOT';
277 14         48 my $info = {
278             code => $referent,
279             args => $hrSig->{parms}, # TODO remove eventually
280             sig => $hrSig,
281 14         52  
282             # For error messages
283             filename => $filename,
284             linenum => $linenum,
285 19         98 candidate_name => $funcname
286             };
287             push @{$multi_def->{impls}}, $info;
288              
289             } #M
290             EOT
291              
292 19         32 _hlog { "M code:\n$code\n" } 2;
  19         81  
293             return $code;
294 11     11   81 } #_make_M
  11         30  
  11         87  
295              
296             # Validate a signature and convert text to usable objects
297 14     24   91 my ($signature, $multi_def, $funcname, $package, $filename, $linenum) = @_;
  10         44  
298 14         826 my ($saw_positional, $saw_named);
299              
300             my $args = $signature->{parms};
301             my $temp;
302             foreach (@$args) {
303 28     48   97  
304 28         49 # Is the argument valid in this package?
305             my $name = $_->{name};
306 28         53 unless($multi_def->{possible_params}->{$name}) {
307 28         40 die "Argument $name is not listed on the 'use Sub::Multi::Tiny' line (used by $funcname at $filename\:$linenum";
308 28         68 }
309              
310             # Is the argument out of order?
311 33         61 die "Positional arguments must precede named arguments"
312 33 100       107 if $saw_named && !$_->{named};
313 1         711  
314             # Inflate type constraint, if any
315             if($_->{type}) {
316             _hlog { In => $package, "evaluating type '$_->{type}'" };
317             $temp = eval _line_mark_string <<EOT;
318 32 50 33     83 do {
319             package $package;
320             $_->{type} # Anything meaningful in the calling package is OK
321 32 100       76 }
322 14     31   85 EOT
  13         60  
323 14         146 die "In $package: Could not understand type '$_->{type}': $@" if $@;
324             $_->{type} = $temp;
325             }
326              
327             # Inflate where clause, if any, into a closure
328             if($_->{where}) {
329 14 50       13417 _hlog { In => $package, "evaluating 'where' clause '$_->{where}'" };
330 14         40 $temp = eval _line_mark_string <<EOT;
331             do {
332             package $package;
333             sub $_->{where} # Anything meaningful in the calling package is OK
334 32 100       73 }
335 8     9   60 EOT
  7         43  
336 8         80 die "In $package: Could not understand 'where' clause '$_->{where}': $@" if $@;
337             $_->{where} = $temp;
338             }
339              
340             # Remember data for later
341             $saw_named ||= $_->{named};
342 8 50       31 $saw_positional ||= !$_->{named};
343 8         19  
344             }
345             } # _check_and_inflate_sig
346              
347 32   33     538 # Create a dispatcher
348 32   66     164 my $hr = shift;
349             die "No implementations given for $hr->{defined_in}"
350             unless @{$hr->{impls}};
351              
352             my $custom_dispatcher = do {
353             no strict 'refs';
354             *{ $hr->{defined_in} . '::MakeDispatcher' }{CODE}
355 13     15   32 };
356              
357 13 50       57 return $custom_dispatcher->($hr) if defined $custom_dispatcher;
  13         71  
358              
359 13         27 # Default dispatcher
360 16     12   104 require Sub::Multi::Tiny::Dispatcher::Default;
  13         34  
  13         1422  
361 13         103 return Sub::Multi::Tiny::Dispatcher::Default::MakeDispatcher($hr);
362 13         28 } #_make_dispatcher
363              
364 13 100       72 1;
365             # Rest of the documentation {{{1
366              
367 6         42 =head1 CUSTOM DISPATCH
368 6         30  
369             This module includes a default dispatcher (implemented in
370             L<Sub::Multi::Tiny::Dispatcher::Default>. To use a different dispatcher,
371             define or import a sub C<MakeDispatcher()> into the package before
372             compilation ends. That sub will be called to create the dispatcher.
373             For example:
374              
375             {
376             package main::foo;
377             use Sub::Multi::Tiny;
378             sub MakeDispatcher { return sub { ... } }
379             }
380              
381             or
382              
383             {
384             package main::foo;
385             use Sub::Multi::Tiny;
386             use APackageThatImportsMakeDispatcherIntoMainFoo;
387             }
388              
389             As a shortcut, you can specify a dispatcher on the C<use> line. For example:
390              
391             use Sub::Multi::Tiny qw(D:Foo $var);
392              
393             will use dispatcher C<Sub::Multi::Tiny::Dispatcher::Foo>. Any name with a
394             double-colon will be used as a full package name. E.g., C<D:Bar::Quux> will
395             use dispatcher C<Bar::Quux>. If C<Foo> does not include a double-colon,
396             C<Sub::Multi::Tiny::Dispatcher::> will be prepended.
397              
398             =head1 DEBUGGING
399              
400             For extra debug output, set L<Sub::Multi::Tiny::Util/$VERBOSE> to a positive
401             integer. This has to be set at compile time to have any effect. For example,
402             before creating any multisubs, do:
403              
404             use Sub::Multi::Tiny::Util '*VERBOSE';
405             BEGIN { $VERBOSE = 2; }
406              
407             =head1 RATIONALE
408              
409             =over
410              
411             =item *
412              
413             To be able to use multisubs in pre-5.14 Perls with only built-in
414             language facilities. This will help me make my own modules backward
415             compatible with those Perls.
416              
417             =item *
418              
419             To learn how it's done! :)
420              
421             =back
422              
423             =head1 SEE ALSO
424              
425             I looked at these but decided not to use them for the following reasons:
426              
427             =over
428              
429             =item L<Class::Multimethods>
430              
431             I wanted a syntax that used normal C<sub> definitions as much as possible.
432             Also, I was a bit concerned by LPALMER's experience that it "does what you
433             don't want sometimes without saying a word"
434             (L<Class::Multimethods::Pure/Semantics>).
435              
436             Other than that, I think this looks pretty decent (but haven't tried it).
437              
438             =item L<Class::Multimethods::Pure>
439              
440             Same desire for C<sub> syntax. Additionally, the last update was in 2007,
441             and the maintainer hasn't uploaded anything since. Other than that, I think
442             this also looks like a decent option (but haven't tried it).
443              
444             =item L<Dios>
445              
446             This is a full object system, which I do not need in my use case.
447              
448             =item L<Logic>
449              
450             This one is fairly clean, but uses a source filter. I have not had much
451             experience with source filters, so am reluctant.
452              
453             =item L<Kavorka::Manual::MultiSubs> (and L<Moops>)
454              
455             Requires Perl 5.14+.
456              
457             =item L<MooseX::MultiMethods>
458              
459             I am not ready to move to full L<Moose>!
460              
461             =item L<MooseX::Params>
462              
463             As above.
464              
465             =item L<Sub::Multi>
466              
467             The original inspiration for this module, whence this module's name.
468             C<Sub::Multi> uses coderefs, and I wanted a syntax that used normal
469             C<sub> definitions as much as possible.
470              
471             =item L<Sub::SmartMatch>
472              
473             This one looks very interesting, but I haven't used smartmatch enough
474             to be fully comfortable with it.
475              
476             =back
477              
478             =head1 SUPPORT
479              
480             You can find documentation for this module with the perldoc command.
481              
482             perldoc Sub::Multi::Tiny
483              
484             You can also look for information at:
485              
486             =over
487              
488             =item * GitHub: The project's main repository and issue tracker
489              
490             L<https://github.com/cxw42/Sub-Multi-Tiny>
491              
492             =item * MetaCPAN
493              
494             L<Sub::Multi::Tiny>
495              
496             =item * This distribution
497              
498             See the tests in the C<t/> directory distributed with this software
499             for usage examples.
500              
501             =back
502              
503             =head1 BUGS
504              
505             =over
506              
507             =item * It's not as tiny as I thought it would be!
508              
509             =item * This isn't Damian code ;) .
510              
511             =back
512              
513             =head1 AUTHOR
514              
515             Chris White E<lt>cxw@cpan.orgE<gt>
516              
517             =head1 LICENSE
518              
519             Copyright (C) 2019 Chris White E<lt>cxw@cpan.orgE<gt>
520              
521             This library is free software; you can redistribute it and/or modify
522             it under the same terms as Perl itself.
523              
524             =cut
525              
526             # }}}1
527             # vi: set fdm=marker: #