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