File Coverage

blib/lib/Data/Polymorph.pm
Criterion Covered Total %
statement 114 135 84.4
branch 43 58 74.1
condition 12 18 66.6
subroutine 27 28 96.4
pod 16 16 100.0
total 212 255 83.1


line stmt bran cond sub pod time code
1              
2 3     3   82582 use warnings;
  3         7  
  3         111  
3 3     3   16 use strict;
  3         5  
  3         243  
4              
5             package Data::Polymorph;
6              
7 3     3   17 use Carp;
  3         10  
  3         302  
8 3     3   18 use Scalar::Util qw( blessed looks_like_number );
  3         6  
  3         355  
9 3     3   4383 use UNIVERSAL qw( isa can );
  3         45  
  3         16  
10              
11             =head1 NAME
12              
13             Data::Polymorph - Yet another approach for polymorphism.
14              
15             =head1 VERSION
16              
17             Version 0.01
18              
19             =cut
20              
21             our $VERSION = '0.01';
22              
23             =head1 SYNOPSIS
24              
25             my $poly = Data::Polymorph->new;
26            
27             ## defining external method 'freeze'
28            
29             $poly->define( 'FileHandle' => freeze => sub{
30             "do{ require Symbol; bless Symbol::gensym(), '".ref($_[0])."'}"
31             } );
32            
33             $poly->define( "UNIVERSAL" => freeze => sub{
34             use Data::Dumper;
35             sprintf( 'do{ my %s }', Dumper $_[0]);
36             });
37            
38             ## it returns `undef'
39             FileHandle->can('freeze');
40             UNIVERSAL->('freeze');
41            
42             ###
43             ### applying defined method.
44             ###
45            
46             ## returns "do{ requier Symbol; bless Symbol::gensym(), 'FileHandle'}"
47             $poly->apply( FileHandle->new , 'freeze' );
48              
49             =head1 DESCRIPTION
50              
51             This module provides gentle way of polymorphic behaviors definition
52             for special cases that aren't original concerns.
53              
54             Applying this solution dissipates necessity for making an original
55             namespace dirty.
56              
57             =head1 ATTRIBUTES
58              
59             =over 4
60              
61             =item C
62              
63             ##
64             ## If external method "foo" is not defined into the $poly...
65             ##
66            
67             $poly->runs_native(1);
68             $poly->apply($obj, foo => $bar ); # ... same as $obj->foo($bar)
69             $poly->runs_native(0);
70             $poly->apply($obj, foo => $bar ); # ... die
71              
72             If this value is true and the object uses C
73             when the method is not defined.
74              
75             =item C
76              
77             The dictionary of class methods.
78              
79             =item C
80              
81             The dictionary of type methods.
82              
83             =back
84              
85             =head1 METHODS
86              
87             =over 4
88              
89             =item C
90              
91             $poly = Data::Polymorph->new();
92             $poly = Data::Polymorph->new( runs_native => 0 );
93             $poly = Data::Polymorph->new( runs_native => 1 );
94              
95             Constructs and returns a new instance of this class.
96              
97             =cut
98              
99              
100              
101             {
102             my @Template =
103             (
104             [ class_methods => sub{{}} ],
105             [ type_methods => sub{
106             return
107             [
108             [Undef => sub{ !defined( $_[1] ); },{},'Any'],
109             [ScalarRef => sub{ isa( $_[1], 'SCALAR' ) },{},'Ref'],
110             [CodeRef => sub{ isa( $_[1], 'CODE' ) },{},'Ref'],
111             [ArrayRef => sub{ isa( $_[1], 'ARRAY' ) },{},'Ref'],
112             [HashRef => sub{ isa( $_[1], 'HASH' ) },{},'Ref'],
113             [GlobRef => sub{ isa( $_[1], 'GLOB' ) },{},'Ref'],
114             [RefRef => sub{ isa( $_[1], 'REF' ) },{},'Ref'],
115             [Ref => sub{ ref( $_[1] ) and 1 },{},'Defined'],
116             [Num => sub{ looks_like_number( $_[1] ) },{},'Value'],
117             [Glob => sub{ isa(\$_[1],'GLOB' ) },{},'Value'],
118             [Str => sub{ isa(\$_[1],'SCALAR'); },{},'Value'],
119             [Value => sub{ 1 },{},'Defined'],
120             [Defined => sub{ 1 },{},'Any'],
121             [Any => sub{ 1 },{},undef],
122             ]
123             }],
124              
125             [ _dic => sub{
126             my $self = shift;
127             return { map{ ($_->[0] , $_)} @{$self->type_methods} };
128             }],
129              
130             [ runs_native => sub{0} ],
131             );
132              
133             sub{
134             my ( $caller ) = caller;
135             foreach (@_){
136             my $field = $_;
137 3     3   5538 my $glob = do{ no strict 'refs'; \*{"${caller}::$field"} };
  3         6  
  3         4174  
138             *{$glob} = sub ($;$){
139 186     186   184 my $self = shift;
140 186 50       1176 return $self->{$field} unless @_;
141 0         0 $self->{$field} = shift;
142             };
143             }
144             }->( map { $_->[0]} @Template );
145              
146             sub new {
147 3     3 1 556 my ($self, %args) = @_;
148 3   33     34 $self = bless {} , (blessed $self) || $self;
149 3         37 foreach my $spec ( @Template ){
150 12         39 $self->{$spec->[0]} = $spec->[1]->($self);
151             }
152 3 50       17 $self->runs_native(1) if $args{runs_native};
153 3         10 $self;
154             }
155             }
156              
157              
158              
159             =item C
160              
161             $type = $poly->type( 123 ); # returns 'Num'
162              
163             Returns the type name of the given object. Types are below.
164              
165             Any
166             Undef
167             Defined
168             Value
169             Num
170             Str
171             Glob
172             Ref
173             ScalarRef
174             HashRef
175             ArrayRef
176             CodeRef
177             RefRef
178              
179             They seem like L Types.
180              
181             Actually, I designed these types based on the man pages from
182             L.
183             Because these were not designed for constraint, they never relate with
184             L types.
185              
186             =item C
187              
188             $poly->is_type('Any') ; # => 1
189             $poly->is_type('Str') ; # => 1
190             $poly->is_type('UNIVERSAL') ; # => 0
191              
192             Returns true if given name is a defined type name. Otherwise,
193             returns false.
194              
195             =item C
196              
197             $type = $poly->super_type('Str'); # => Value
198             $type = $poly->super_type('Undef'); # => Any
199              
200             Returns name of the type which is the super type of the given type name.
201              
202             =item C
203              
204             $type = $poly->class( $obj );
205              
206             Returns class name or type name of the given object.
207              
208             =cut
209              
210             sub type {
211 50     50 1 5970 my ( $self, $obj ) = @_;
212 50         60 foreach my $slot ( @{$self->type_methods} ) {
  50         74  
213 258 100       354 return $slot->[0] if $slot->[1]->($self, $obj) ;
214             }
215             }
216              
217             sub is_type {
218 37     37 1 39 my ($self, $type) = @_;
219 37 100       53 (exists $self->_dic->{$type}) ? 1 : 0;
220             }
221              
222             sub super_type {
223 1     1 1 19 my ($self, $type) = @_;
224 1 50       12 confess "$type is not a type" unless $self->is_type( $type );
225 0   0     0 ($self->_dic->{$type} || [])->[3];
226             }
227              
228             sub class {
229 2     2 1 3 my ( $self, $obj ) = @_;
230 2 50       262 blessed( $obj ) or $self->type( $obj );
231             }
232              
233             =item C
234              
235             $poly->define_type_method('ArrayRef' => 'values' => sub{ @$_[0]});
236             $poly->define_type_method('HashRef' => 'values' => sub{ values %$_[0]});
237             $poly->define_type_method('Any' => 'values' => sub{ $_[0] });
238              
239             Defines a method for the given type.
240              
241             =item C
242              
243             $poly->define_class_method( 'Class::Name' => 'method' => sub{
244             # code reference
245             } );
246              
247             Defines an external method for a given class which can be appliabled
248             by the instance of this class.
249              
250             =item C
251              
252             $poly->define('Class::Name' => 'method' => sub{ ... } );
253             $poly->define('Undef' => 'method' => sub{ ... } );
254              
255             Defines a method for a type or a class.
256              
257             =cut
258              
259             sub define_type_method {
260 10     10 1 1038 my ( $self, $class, $method , $code ) = @_;
261 10         30 foreach my $slot ( @{$self->type_methods}) {
  10         13  
262 85 100       686 next unless $slot->[0] eq $class;
263 9         26 return $slot->[2]->{$method} = $code;
264             }
265 1         110 confess "unknown type: $class";
266             }
267              
268             sub define_class_method {
269 7     7 1 741 my ( $self, $class, $method , $code ) = @_;
270 7   100     15 my $slot = ($self->class_methods->{$method} ||= []);
271 7         8 my $i = 0;
272 7         20 for(; $i < scalar @$slot ; $i++){
273 13         14 my $klass = $slot->[$i]->[0];
274              
275 13 50       16 if( $klass eq $class ){
276 0         0 $slot->[$i]->[1] = $code;
277 0         0 return;
278             }
279              
280 13 100       58 last if isa $class => $klass;
281             }
282 7         26 splice @$slot, $i, 0, [$class => $code];
283             }
284              
285             sub define {
286 15     15 1 499 my ( $self, $class, $method, $code ) = @_;
287 15 100       27 goto ( $self->is_type( $class )
288             ? \&define_type_method
289             : \&define_class_method );
290             }
291              
292              
293             =item C
294              
295             $meth = $poly->type_method( 'ArrayRef' => 'values' );
296              
297             Returns a CODE reference which is invoked as the method of given type.
298              
299             =item C
300              
301             $meth = $poly->super_type_method( 'ArrayRef' => 'values' );
302              
303             Returns a CODE reference which is invoked as the super method of given type.
304              
305             =cut
306              
307             sub type_method {
308 13     13 1 731 my ( $self, $type, $method ) = @_;
309 13 100       26 confess "$type is not a type" unless $self->is_type( $type );
310 12         25 while ( $type ){
311 22         35 my $slot = $self->_dic->{$type};
312 22         40 my $code = $slot->[2]->{$method};
313 22 100       81 return $code if $code;
314 11         24 $type = $slot->[3];
315             }
316 1         9 undef;
317             }
318              
319             sub super_type_method {
320 8     8 1 712 my ($self, $type, $method ) = @_;
321 8 100       16 confess "$type is not a type" unless $self->is_type( $type );
322 7         10 my $count = 0;
323 7         17 for (my $slot; $type ; $type = $slot->[3] ){
324 20         36 $slot = $self->_dic->{$type};
325 20         31 my $code = $slot->[2]->{$method};
326 20 100       46 next unless $code;
327 12 100       38 return $code if $count;
328 6         13 $count++;
329             }
330 1         12 undef;
331             }
332              
333             =item C
334              
335             $meth = $poly->class_method( 'A::Class' => 'method' );
336             ($poly->apply( 'A::Class' => $method ) or
337             sub{ confess "method $method is not defined" } )->( $args .... );
338              
339             Returns a CODE reference which is invoked as the method of given class.
340              
341             =item C
342              
343             $super = $poly->super_class_method( 'A::Class' => 'method' );
344             ($poly->apply( 'A::Class' => $method ) or
345             sub{ confess "method $method is not defined" } )->( $args .... );
346              
347             Returns a CODE reference which is invoked as the super method of given class.
348              
349             =cut
350              
351             sub class_method {
352 23     23 1 30 my ( $self, $class, $method ) = @_;
353 23   100     38 my $slot = ($self->class_methods->{$method} ||= []);
354 23         43 foreach my $meth ( @$slot ){
355 99 100       330 next unless isa( $class, $meth->[0] );
356 20         69 return $meth->[1];
357             }
358             }
359              
360             sub super_class_method {
361 11     11 1 14 my ( $self, $class, $method ) = @_;
362 11   50     19 my $slot = ($self->class_methods->{$method} ||= []);
363 11         12 my $count = 0;
364 11         17 foreach my $meth ( @$slot ){
365 51 100       160 next unless isa( $class, $meth->[0] );
366 20 100       47 return $meth->[1] if $count;
367 11         16 $count++;
368             }
369             }
370              
371             =item C
372              
373             $code = $poly->method( [] => 'values' );
374             $code = $poly->method( qr{foo} => 'values' );
375             $code = $poly->method( FileHandle->new => 'values' );
376              
377             Returns a CODE reference which is invoked as the method of given object.
378              
379             =item C
380              
381             $code = $poly->super_method( [] => 'values' );
382             $code = $poly->super_method( qr{foo} => 'values' );
383             $code = $poly->super_method( FileHandle->new => 'values' );
384             $code = $poly->super_method( 'Any' => 'values' ); # always undef
385              
386             Returns a CODE reference which is invoked as the super method of given object.
387              
388             =cut
389              
390             sub method {
391 19     19 1 23 my ( $self, $obj, $method ) = @_;
392 19         44 my $class = blessed( $obj );
393 19         41 my $type = $self->type( $obj );
394 19 100 66     72 ($class
395             ? ( $self->class_method( $class, $method ) or
396             $self->type_method( $type, $method ) or
397             ( $self->runs_native and UNIVERSAL::can( $obj , $method ) ))
398             : $self->type_method( $type, $method ));
399             }
400              
401             sub _native_super {
402              
403 0     0   0 my ( $class, $method ) = @_;
404 3     3   20 my $glob = do{ no strict 'refs'; \*{"$class::$method"} };
  3         7  
  3         1342  
  0         0  
  0         0  
  0         0  
405 3     3   18 my @isa = do{ no strict 'refs'; @{"${class}::ISA"} };
  3         14  
  3         1490  
  0         0  
  0         0  
  0         0  
406              
407 0 0       0 if( *{$glob}{CODE} ){
  0         0  
408 0         0 foreach my $parent ( @isa ){
409 0         0 my $code = UNIVERSAL::can( $parent, $method );
410 0 0       0 return $code if $code;
411             }
412             }
413             else {
414 0         0 foreach my $parent ( @isa ){
415 0         0 my $code = _native_super( $parent, $method );
416 0 0       0 return $code if $code;
417             }
418             }
419             }
420              
421             sub super_method {
422 18     18 1 26 my ( $self, $obj, $method ) = @_;
423 18         44 my $class = blessed( $obj );
424 18         39 my $type = $self->type( $obj );
425              
426 18 100       44 if ( $class ){
427 12         27 my $uni = $self->class_method( UNIVERSAL => $method );
428 12 100       25 if( $class eq 'UNIVERSAL' ) {
429              
430 1 50       5 return $self->type_method( $type => $method ) if $uni;
431              
432             }
433             else {
434              
435 11         24 my $code = $self->super_class_method( $class, $method );
436 11 100       53 return $code if $code;
437              
438 2 50       7 if( $self->runs_native ) {
439 0         0 $code = _native_super( $class, $method );
440 0 0       0 return $code if $code;
441             }
442              
443 2 100       9 return $self->type_method( $type => $method ) if $uni;
444             }
445             }
446              
447 7         17 $self->super_type_method( $type => $method );
448             }
449              
450              
451             =item C
452              
453             $poly->apply( $obj => 'method' => $arg1, $arg1 , $arg3 .... );
454              
455             Invokes a method which was defined.
456              
457             =item C
458              
459             $poly->super( $obj => 'method' => $arg1, $arg1 , $arg3 .... );
460              
461             Invokes a super method which was defined..
462              
463             =back
464              
465             =cut
466              
467              
468             sub apply {
469 19     19 1 7834 my $self = shift;
470 19         26 my $obj = $_[0];
471 19         29 my $method = splice @_, 1, 1;
472             goto ( $self->method( $obj => $method ) or
473 1     1   4 sub{ confess sprintf( 'method "%s" is not defined in %s',
474             $method,
475 19   100     67 $self->class($obj)) });
476             }
477              
478             sub super {
479 18     18 1 7360 my $self = shift;
480 18         56 my $obj = $_[0];
481 18         26 my $method = splice @_, 1, 1;
482             goto ( $self->super_method( $obj => $method ) or
483 1     1   8 sub{ confess sprintf( 'method "SUPER::%s" is not defined in %s',
484             $method,
485 18   100     38 $self->class($obj)) });
486             }
487              
488             1; # End of Data::Polymorph
489              
490             __END__