File Coverage

blib/lib/Params/PatternMatch.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package Params::PatternMatch;
2              
3             # ABSTRACT: Pattern match-based argument binding for Perl.
4              
5 1     1   34334 use strict;
  1         3  
  1         44  
6 1     1   5 use warnings;
  1         2  
  1         30  
7 1     1   6 use B;
  1         15  
  1         73  
8 1     1   6 use Carp;
  1         2  
  1         81  
9 1     1   289725 use Data::Compare;
  1         601642  
  1         9  
10 1     1   611445 use Exporter::Lite;
  1         854  
  1         7  
11 1     1   74 use Scalar::Util qw/blessed/;
  1         2  
  1         69  
12 1     1   572 use TryCatch;
  0            
  0            
13              
14             our $COMPARATOR = Data::Compare->new;
15              
16             our @EXPORT_OK = qw/as case match otherwise rest then/;
17              
18             our $VERSION = '0.01';
19              
20             our @args;
21              
22             sub as(&) { @_ }
23              
24             sub case {
25             my $action = pop;
26             Carp::croak('Not a CodeRef.') if ref $action ne 'CODE';
27              
28             my ($i, $j) = (0, 0);
29             for (; $i < @args and $j < @_; ++$i, ++$j) {
30             if (is_slurp_arg($_[$j])) {
31             $_[$j]->set(@args[$i .. $#args]);
32             $i = $#args;
33             next;
34             }
35             if (is_lvalue($_[$j])) {
36             $_[$j] = $args[$i];
37             next;
38             }
39             next if $COMPARATOR->Cmp($args[$i], $_[$j]) != 0;
40              
41             return; # Pattern didn't match.
42             }
43             return unless $i == @args and $j == @_ or is_slurp_arg($_[$j]);
44              
45             die Params::PatternMatch::Values->new($action->(@args));
46             }
47              
48             sub is_lvalue($) { +(B::svref_2object(\$_[0])->FLAGS & B::SVf_READONLY) == 0 }
49              
50             sub is_slurp_arg($) {
51             blessed $_[0] and $_[0]->isa('Params::PatternMatch::SlurpArg');
52             }
53              
54             sub match {
55             my $patterns = pop;
56             Carp::croak('Not a CodeRef.') if ref $patterns ne 'CODE';
57              
58             local *args = \@_;
59             try {
60             $patterns->();
61             } catch (Params::PatternMatch::Values $retval) {
62             return $retval->values;
63             } catch ($error) {
64             die $error;
65             };
66             }
67              
68             sub otherwise(&) {
69             Carp::croak('Not a CodeRef.') if ref $_[0] ne 'CODE';
70             die Params::PatternMatch::Values->new($_[0]->(@args));
71             }
72              
73             sub rest(\@) { Params::PatternMatch::SlurpArg->new($_[0]) }
74              
75             sub then(&) { @_ }
76              
77             package Params::PatternMatch::SlurpArg;
78              
79             sub new { bless $_[1] => $_[0] }
80              
81             sub set { @{ $_[0] } = @_[1 .. $#_ ] }
82              
83             package Params::PatternMatch::Values;
84              
85             sub new { bless \@_ => shift }
86              
87             sub values { wantarray ? @{ $_[0] } : $_[0][0] }
88              
89             1;
90              
91             __END__
92              
93             =pod
94              
95             =encoding UTF-8
96              
97             =head1 NAME
98              
99             Params::PatternMatch - Pattern match-based argument binding for Perl.
100              
101             =head1 VERSION
102              
103             version 0.01
104              
105             =head1 SYNOPSIS
106              
107             use Carp ();
108             use Params::PatternMatch qw/as case match otherwise rest then/;
109            
110             sub sum {
111             match @_ => as {
112             my ($n, @rest);
113             case +() => then { 0 };
114             case $n, rest(@rest) => then { $n + sum(@rest) };
115             };
116             }
117            
118             say sum(1 .. 10); # 55
119            
120             sub factorial {
121             match @_ => as {
122             my $n;
123             case 0 => then { 1 };
124             case $n => then { $n * factorial($n - 1) };
125             otherwise { Carp::croak('factorial: requires exactly 1 argument.') };
126             };
127             }
128            
129             say factorial(5); # 120
130             say factorial(1 .. 10); # Error
131              
132             =head1 DESCRIPTION
133              
134             This extension provides keywords for pattern match-based argument binding like functional languages, such as Scala or ML.
135              
136             =head2 CAVEAT
137              
138             Note that the implementation is not tail call-optimized; Unlike real functional languages, you cannot use recursive C<match> instead of loop.
139              
140             =head1 FUNCTIONS
141              
142             None of them liseted below are exported by default. So you need to C<import> explicitly.
143              
144             =head2 as(\&block), then(\&block)
145              
146             Synonyms for C<sub>.
147              
148             =head2 case(@pattern, \&then)
149              
150             Returns evaluation value for C<&then> if the C<@pattern> matched with C<match>'s arguments.
151              
152             =head2 match(@arguments, \&patterns)
153              
154             Returns evaluation value for C<&patterns>.
155             C<@arguments> is passed as C<@_> to C<case>/C<otherwise> blocks.
156              
157             =head2 otherwise(\&block)
158              
159             Returns evaluation value for &block without pattern match.
160              
161             =head2 rest(@slurped)
162              
163             Slurps all the rest unbound arguments.
164              
165             =head1 PATTERN MATCH RULE
166              
167             Now I'll describe how the pattern match performs. C<match>'s arguments are element-wise-compared with C<case>'s pattern.
168              
169             If an element in pattern is:
170              
171             =over 4
172              
173             =item an lvalue (i.e., a variable)
174              
175             Always matches (except if no corresponding argument exists.) Corresponding argument will be assigned as its value.
176              
177             =item C<rest>
178              
179             Always matches. All the rest arguments will be slurped.
180              
181             =item an rvalue (i.e., an immediate value)
182              
183             The value will be compared with corresponding argument using L<Data::Compare>.
184              
185             =back
186              
187             The C<Data::Compare> instance used for rvalue comparison is stored in C<$Params::PatternMatch::COMPARATOR>. You can override match rule by C<local>ize the comparator:
188              
189             {
190             local $Params::PatternMatch::COMPARATOR = Data::Compare->new(...);
191             # Or anything having Cmp() method:
192             package MyComparator {
193             sub new { ... }
194            
195             # Returns 1 if the given $x and $y are equivalent, 0 otherwise.
196             sub Cmp { my ($self, $x, $y) = @_; ... }
197             }
198             local $Params::PatternMatch::COMPARATOR = MyComparator->new(...);
199            
200             match @_ => as { ... };
201             }
202              
203             =head1 AUTHOR
204              
205             Koichi SATOH <sato@seesaa.co.jp>
206              
207             =head1 COPYRIGHT AND LICENSE
208              
209             This software is Copyright (c) 2014 by Koichi SATOH.
210              
211             This is free software, licensed under:
212              
213             The MIT (X11) License
214              
215             =cut