File Coverage

blib/lib/Symbol/Opaque.pm
Criterion Covered Total %
statement 81 84 96.4
branch 2 2 100.0
condition 5 12 41.6
subroutine 29 32 90.6
pod 0 4 0.0
total 117 134 87.3


line stmt bran cond sub pod time code
1             package Symbol::Opaque;
2              
3             our $VERSION = '0.03';
4              
5 1     1   24150 use 5.006001;
  1         4  
  1         38  
6 1     1   7 use strict;
  1         2  
  1         373  
7 1     1   6 use warnings;
  1         7  
  1         54  
8 1     1   5 no warnings 'uninitialized';
  1         3  
  1         44  
9 1     1   1583 use Class::Multimethods::Pure;
  1         20084  
  1         7  
10 1     1   89 use Exporter;
  1         2  
  1         40  
11 1     1   5 use Scalar::Util qw;
  1         2  
  1         48  
12 1     1   5 use base 'Exporter';
  1         2  
  1         337  
13              
14             our @EXPORT = qw;
15              
16             sub _() {
17 1     1   7 Symbol::Opaque::Anything->new;
18             }
19              
20             sub free($) {
21 18     18 0 1346 Symbol::Opaque::Free->new(\$_[0]);
22             }
23              
24             sub id($) {
25 0     0 0 0 Symbol::Opaque::Id->new($_[0]);
26             }
27              
28             sub makesymdef {
29 2     2 0 3 my ($name) = @_;
30             sub {
31 39     39   2835 my @args;
32 39         82 for my $i (0..$#_) {
33 53 100 66     180 if (!defined $_[$i] && !readonly $_[$i]) {
34 14         29 push @args, free $_[$i];
35             }
36             else {
37 39         106 push @args, $_[$i];
38             }
39             }
40 39         114 Symbol::Opaque::Symbol->new($name, @args);
41 2         11 };
42             }
43              
44             sub defsym {
45 2     2 0 11 my ($name) = @_;
46 1     1   6 no strict 'refs';
  1         1  
  1         652  
47 2         4 my $package = caller;
48 2         6 *{"$package\::$name"} = makesymdef $name;
  2         11  
49             }
50              
51             multi UNIFY => (Any, Any) => sub {
52             my ($a, $b) = @_;
53             $a eq $b and sub { };
54             };
55              
56             multi UNIFY => ('Symbol::Opaque::Free', Any) => sub {
57             my ($var, $thing) = @_;
58             $var->bind($thing);
59             };
60              
61             multi UNIFY => (subtype('Symbol::Opaque::Free', sub { $_[0]->bound }), Any) => sub {
62             my ($var, $thing) = @_;
63             UNIFY($var->value, $thing);
64             };
65              
66             multi UNIFY => ('Symbol::Opaque::Symbol', Any) => sub {
67             0;
68             };
69              
70             multi UNIFY => ('Symbol::Opaque::Symbol', 'Symbol::Opaque::Symbol') => sub {
71             my ($sa, $sb) = @_;
72             return 0 unless $sa->name eq $sb->name;
73              
74             UNIFY([$sa->args], [$sb->args]);
75             };
76              
77             multi UNIFY => ('Symbol::Opaque::Anything', Any) => sub {
78             sub { };
79             };
80              
81             multi UNIFY => ('ARRAY', 'ARRAY') => sub {
82             my ($a, $b) = @_;
83             return 0 unless @$a == @$b;
84            
85             my @rollback;
86             for my $i (0..$#$a) {
87             my $code = UNIFY($a->[$i], $b->[$i]);
88             if ($code) {
89             push @rollback, $code;
90             }
91             else {
92             $_->() for @rollback;
93             return 0;
94             }
95             }
96              
97             return sub { $_->() for @rollback };
98             };
99              
100             # Hash-hash unification is a little subtle.
101             # The right hash has to have every key-value pair as the left hash,
102             # but the right may have extra keys and that's okay.
103             multi UNIFY => ('HASH', 'HASH') => sub {
104             my ($a, $b) = @_;
105              
106             my @keys = keys %$a;
107             for (@keys) {
108             return 0 unless exists $b->{$_};
109             }
110             UNIFY([ @$a{@keys} ], [ @$b{@keys} ]);
111             };
112              
113             package Symbol::Opaque::Ops;
114              
115 1     1   13 use Class::Multimethods::Pure multi => 'UNIFY';
  1         2  
  1         4  
116              
117             use overload
118 15     15   53 '<<' => sub { ! !UNIFY($_[0], $_[1]) },
119 0     0   0 '>>' => sub { ! !UNIFY($_[1], $_[0]) },
120 0     0   0 '""' => sub { overload::StrVal($_[0]) },
121 1     1   2294 ;
  1         1324  
  1         14  
122              
123             package Symbol::Opaque::Symbol;
124              
125 1     1   101 use base 'Symbol::Opaque::Ops';
  1         1  
  1         702  
126              
127             sub new {
128 39     39   71 my ($class, $name, @args) = @_;
129 39   33     404 bless {
130             name => $name,
131             args => \@args,
132             } => ref $class || $class;
133             }
134              
135             sub name {
136 38     38   47 my ($self) = @_;
137 38         102 $self->{name};
138             }
139              
140             sub args {
141 38     38   40 my ($self) = @_;
142 38         34 @{$self->{args}};
  38         134  
143             }
144              
145             package Symbol::Opaque::Free;
146              
147 1     1   11 use base 'Symbol::Opaque::Ops';
  1         2  
  1         593  
148              
149             sub new {
150 18     18   32 my ($class, $ref) = @_;
151 18         50 undef $$ref;
152 18   33     161 bless {
153             ref => $ref,
154             } => ref $class || $class;
155             }
156              
157             sub bind {
158 11     11   13 my ($self, $thing) = @_;
159 11         13 ${$self->{ref}} = $thing;
  11         18  
160             sub {
161 3     3   4 undef ${$self->{ref}};
  3         10  
162 11         52 };
163             }
164              
165             sub bound {
166 15     15   25 my ($self) = @_;
167 15         15 defined ${$self->{ref}};
  15         105  
168             }
169              
170             sub value {
171 4     4   7 my ($self) = @_;
172 4         6 ${$self->{ref}};
  4         15  
173             }
174              
175             package Symbol::Opaque::Anything;
176              
177 1     1   19 use base 'Symbol::Opaque::Ops';
  1         2  
  1         524  
178              
179             sub new {
180 1     1   3 my ($class) = @_;
181 1   33     9 bless {} => ref $class || $class;
182             }
183              
184             1;
185              
186             =head1 NAME
187              
188             Symbol::Opaque - ML-ish data constructor pattern matching
189              
190             =head1 SYNOPSIS
191              
192             use Symbol::Opaque;
193              
194             BEGIN {
195             defsym('foo'); # define the constructor "foo"
196             defsym('bar'); # define the constructor "bar"
197             }
198              
199             if ( foo(my $x) << foo(4) ) { # bind foo(4) into foo($x)
200             # $x is now 4
201             }
202            
203             if ( foo(13, bar(my $x)) << foo(13, bar("baz")) ) {
204             # $x is now "baz"
205             }
206              
207             if ( foo(my $x) << bar(42) ) {
208             # not executed: foo(X) doesn't match bar(42)
209             }
210              
211             =head1 DESCRIPTION
212              
213             This module allows the creation of data constructors, which can then be
214             conditionally unified like in Haskell or ML. When you use the B
215             operator C<<< << >>>, between two structures, this module tries to bind any
216             I on the left in order to make the structures the same.
217             For example:
218              
219             foo(my $x) << foo(14) # true, $x becomes 14
220              
221             This will make $x equal 14, and then the operator will return true. Sometimes
222             it is impossible to make them the same, and in that case no variables are
223             changed and the operator returns false. For instance:
224              
225             foo(my $x, 10) << foo(20, 21) # impossible: false, $x is undef
226              
227             This makes it possible to write cascades of tests on a value:
228              
229             my $y = foo(20, 21);
230             if (foo("hello", my $x) << $y) {
231             ...
232             }
233             elsif (foo(my $x, 21) << $y) {
234             # this gets executed: $x is 20
235             }
236             else {
237             die "No match";
238             }
239              
240             (Yes, Perl lets you declare the same variable twice in the same cascade -- just
241             not in the same condition).
242              
243             Before you can do this, though, you have to tell Perl that C is such a
244             data constructor. This is done with the exported C routine. It is
245             advisable that you do this in a C block, so that the execution path
246             doesn't have to reach it for it to be defined:
247              
248             BEGIN {
249             defsym('foo'); # foo() is a data constructor
250             }
251              
252             If two different modules both declare a 'foo' symbol, I
253             same>. The reason this isn't dangerous is because the only thing that can ever
254             differ about two symbols is their name: there is no "implementation" defined.
255              
256             The unification performed is I: you can only have free
257             variables on the left side.
258              
259             The unification performed is I: you can mention the same free
260             variable more than once:
261              
262             my $x; # we must declare first when there is more than one mention
263             foo($x, $x) << foo(4, 4); # true; $x = 4
264             foo($x, $x) << foo(4, 5); # false
265              
266             Unification of arrays is performed by comparing them elementwise, just like the
267             arguments of a structure.
268              
269             Unification of hashes is done like so: Every key that the target (left) hash
270             has, the source (right) hash must also, and their values must unify. However,
271             the source hash may have keys that the target hash does not, and the two hashes
272             will still unify. This is so you can support "property lists", and unify
273             against structures that have certain properties.
274              
275             A variable is considered free if it is writable (this is true of all variables
276             that you'll pass in), undefined, and in the top level of a constructor. That
277             is:
278              
279             foo([1, my $x]) << foo([1,2])
280              
281             Will not unify $x, since it is not directly in a data constructor. To get
282             around this, you can explicitly mark variables as free with the C
283             function:
284              
285             foo([1, free my $x]) << foo([1,2]) # success: $x == 2
286              
287             Sometimes you have a situation where you're unifying against a structure,
288             and you want something to be in a position, but you don't care what it is.
289             The C<_> marker is used in this case:
290              
291             foo([1, _]) << foo([1, 2]) # success: no bindings
292            
293             =head1 SEE ALSO
294              
295             L
296              
297             =head1 AUTHOR
298              
299             Luke Palmer