File Coverage

blib/lib/MooX/PDL/Role/Proxy.pm
Criterion Covered Total %
statement 104 108 96.3
branch 16 22 72.7
condition 1 3 33.3
subroutine 31 32 96.8
pod 14 14 100.0
total 166 179 92.7


line stmt bran cond sub pod time code
1             package MooX::PDL::Role::Proxy;
2              
3             # ABSTRACT: treat a container of piddles as if it were a piddle
4              
5 6     6   887134 use 5.010;
  6         41  
6 6     6   35 use strict;
  6         13  
  6         137  
7 6     6   33 use warnings;
  6         14  
  6         391  
8              
9             our $VERSION = '0.06';
10              
11 6     6   3963 use Types::Standard -types;
  6         669435  
  6         114  
12              
13 6     6   35302 use PDL::Primitive ();
  6         128574  
  6         160  
14 6     6   2385 use Hash::Wrap;
  6         10645  
  6         59  
15 6     6   6037 use Scalar::Util ();
  6         20  
  6         106  
16              
17 6     6   4077 use Moo::Role;
  6         29011  
  6         56  
18 6     6   10554 use Lexical::Accessor;
  6         47218  
  6         98  
19              
20 6     6   4212 use namespace::clean;
  6         48262  
  6         63  
21              
22             use constant {
23 6         594 INPLACE_SET => 1,
24             INPLACE_STORE => 2,
25 6     6   4530 };
  6         18  
26              
27 6     6   3801 use MooX::TaggedAttributes -tags => [qw( piddle )];
  6         97616  
  6         42  
28              
29             my $croak = sub {
30             require Carp;
31             goto \&Carp::croak;
32             };
33              
34             lexical_has attr_subs => (
35             is => 'ro',
36             isa => HashRef,
37             reader => \( my $attr_subs ),
38             default => sub { {} },
39             );
40              
41              
42             lexical_has 'is_inplace' => (
43             is => 'rw',
44             clearer => \( my $clear_inplace ),
45             reader => \( my $is_inplace ),
46             writer => \( my $set_inplace ),
47             default => 0
48             );
49              
50              
51             # requires 'clone_with_piddles';
52              
53              
54              
55              
56              
57              
58              
59              
60              
61              
62              
63              
64              
65              
66              
67              
68              
69             has _piddles => (
70             is => 'lazy',
71             isa => ArrayRef [Str],
72             init_arg => undef,
73             clearer => 1,
74             builder => sub {
75 46     46   726 my $self = shift;
76 46         84 [ keys %{ $self->_tags->{piddle} } ];
  46         299  
77             },
78             );
79              
80              
81              
82              
83              
84              
85              
86              
87              
88              
89              
90              
91              
92              
93              
94              
95              
96              
97              
98             sub _apply_to_tagged_attrs {
99 34     34   91 my ( $self, $action ) = @_;
100              
101 34         166 my $inplace = $self->$is_inplace;
102              
103             my %attr = map {
104 68         6424 my $field = $_;
105 68         278 $field => $action->( $self->$field, $inplace );
106 34         624 } @{ $self->_piddles };
  34         839  
107              
108 34 100       1013 if ( $inplace ) {
109 12         76 $self->$clear_inplace;
110              
111 12 100       106 if ( $inplace == INPLACE_SET ) {
    50          
112 11         67 $self->_set_attr( %attr );
113             }
114              
115             elsif ( $inplace == INPLACE_STORE ) {
116 1         5 for my $attr ( keys %attr ) {
117             # $attr{$attr} may be linked to $self->$attr,
118             # so if we reshape $self->$attr, it really
119             # messes up $attr{$attr}. sever it to be sure.
120 2         134 my $pdl = $attr{$attr}->sever;
121 2         13 ( my $tmp = $self->$attr->reshape( $pdl->dims ) ) .= $pdl;
122             }
123             }
124              
125             else {
126 0         0 $croak->( "unrecognized inplace flag value: $inplace\n" );
127             }
128              
129 12         183 return $self;
130             }
131              
132 22         135 return $self->clone_with_piddles( %attr );
133             }
134              
135              
136              
137              
138              
139              
140              
141              
142              
143              
144              
145              
146              
147              
148              
149              
150              
151              
152              
153              
154             sub inplace {
155 13 50   13 1 297840 $_[0]->$set_inplace( @_ > 1 ? $_[1] : INPLACE_SET );
156 13         249 $_[0];
157             }
158              
159              
160              
161              
162              
163              
164              
165              
166              
167              
168              
169              
170              
171              
172              
173              
174              
175              
176              
177             sub inplace_store {
178 1     1 1 3293 $_[0]->$set_inplace( INPLACE_STORE );
179 1         24 $_[0];
180             }
181              
182              
183              
184              
185              
186              
187              
188              
189              
190              
191              
192              
193              
194              
195              
196              
197              
198              
199              
200             sub inplace_set {
201 0     0 1 0 $_[0]->$set_inplace( INPLACE_SET );
202 0         0 $_[0];
203             }
204              
205              
206              
207              
208              
209              
210              
211              
212              
213              
214              
215              
216              
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227              
228             sub set_inplace {
229 3 50   3 1 901 2 == @_ or $croak->( "set_inplace requires two arguments" );
230 3 50       22 $_[1] >= 0
231             && $_[0]->$set_inplace( $_[1] );
232 3         15 return;
233             }
234              
235              
236              
237              
238              
239              
240              
241              
242              
243 9     9 1 10385 sub is_inplace { goto &$is_inplace }
244              
245              
246              
247              
248              
249              
250              
251              
252              
253              
254              
255              
256              
257             sub copy {
258 5     5 1 20439 my $self = shift;
259              
260 5 100       20 if ( $self->is_inplace ) {
261 1         15 $self->set_inplace( 0 );
262 1         4 return $self;
263             }
264              
265 8         394 return $self->clone_with_piddles( map { $_ => $self->$_->copy }
266 4         83 @{ $self->_piddles } );
  4         81  
267             }
268              
269              
270              
271              
272              
273              
274              
275              
276              
277              
278             sub sever {
279 4     4 1 14096 my $self = shift;
280 4         10 $self->$_->sever for @{ $self->_piddles };
  4         131  
281 4         239 return $self;
282             }
283              
284              
285              
286              
287              
288              
289              
290              
291              
292              
293             sub index {
294 16     16 1 60778 my ( $self, $index ) = @_;
295 16     32   126 return $self->_apply_to_tagged_attrs( sub { $_[0]->index( $index ) } );
  32         370  
296             }
297              
298             # is there a use for this?
299             # sub which {
300             # my ( $self, $which ) = @_;
301             # return PDL::Primitive::which(
302             # 'CODE' eq ref $which
303             # ? do { local $_ = $self; $which->() }
304             # : $which
305             # );
306             # }
307              
308              
309              
310              
311              
312              
313              
314              
315              
316              
317              
318             sub at {
319 4     4 1 60566 my ( $self, @idx ) = @_;
320 4         11 wrap_hash( { map { $_ => $self->$_->at( @idx ) } @{ $self->_piddles } } );
  8         371  
  4         79  
321             }
322              
323              
324              
325              
326              
327              
328              
329              
330              
331              
332             sub where {
333 16     16 1 34197 my ( $self, $where ) = @_;
334              
335 16     32   146 return $self->_apply_to_tagged_attrs( sub { $_[0]->where( $where ) } );
  32         168  
336             }
337              
338              
339              
340              
341              
342              
343              
344              
345              
346              
347              
348              
349              
350              
351             sub _set_attr {
352 37     37   3327 my ( $self, %attr ) = @_;
353 37         217 my $subs = $self->$attr_subs;
354              
355 37         246 for my $key ( keys %attr ) {
356 74         424 my $sub = $subs->{$key};
357              
358 74 50       197 if ( !defined $sub ) {
359 74   33     620 Scalar::Util::weaken( $subs->{$key} = $self->can( "_set_${key}" )
360             // $self->can( $key ) );
361 74         147 $sub = $subs->{$key};
362             }
363              
364 74         1172 $sub->( $self, $attr{$key} );
365             }
366              
367 37         443 return $self;
368             }
369              
370              
371              
372              
373              
374              
375              
376              
377              
378              
379              
380              
381              
382              
383              
384              
385              
386              
387              
388              
389              
390              
391              
392             sub qsort {
393              
394 3     3 1 29787 $_[0]->index( $_[0]->qsorti );
395             }
396              
397              
398              
399              
400              
401              
402              
403              
404              
405              
406              
407              
408             sub qsort_on {
409              
410 2     2 1 9463 my ( $self, $attr ) = @_;
411              
412 2         40 $self->index( $attr->qsorti );
413             }
414              
415              
416              
417              
418              
419              
420              
421              
422              
423              
424              
425              
426              
427              
428              
429             sub clip_on {
430              
431 6     6 1 29972 my ( $self, $attr, $min, $max ) = @_;
432              
433 6         16 my $mask;
434              
435 6 100       23 if ( defined $min ) {
    50          
436 4         113 $mask = $attr >= $min;
437 4 100       379 $mask &= $attr < $max
438             if defined $max;
439             }
440             elsif ( defined $max ) {
441 2         18 $mask = $attr < $max;
442             }
443             else {
444 0         0 $croak->( "one of min or max must be defined\n" );
445             }
446              
447 6         265 $self->where( $mask );
448             }
449              
450              
451              
452              
453              
454              
455              
456              
457              
458              
459              
460              
461              
462             sub slice {
463              
464 2     2 1 9745 my ( $self, $slice ) = @_;
465              
466 2     4   13 return $self->_apply_to_tagged_attrs( sub { $_[0]->slice( $slice ) } );
  4         19  
467             }
468              
469              
470              
471             1;
472              
473             #
474             # This file is part of MooX-PDL-Role-Proxy
475             #
476             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
477             #
478             # This is free software, licensed under:
479             #
480             # The GNU General Public License, Version 3, June 2007
481             #
482              
483             __END__
484              
485             =pod
486              
487             =for :stopwords Diab Jerius Smithsonian Astrophysical Observatory
488              
489             =head1 NAME
490              
491             MooX::PDL::Role::Proxy - treat a container of piddles as if it were a piddle
492              
493             =head1 VERSION
494              
495             version 0.06
496              
497             =head1 SYNOPSIS
498              
499             package My::Class;
500              
501             use Moo;
502             use MooX::PDL::Role::Proxy;
503              
504             use PDL;
505              
506             has p1 => (
507             is => 'rw',
508             default => sub { sequence( 10 ) },
509             piddle => 1
510             );
511              
512             has p2 => (
513             is => 'rw',
514             default => sub { sequence( 10 ) + 1 },
515             piddle => 1
516             );
517              
518              
519             sub clone_with_piddles {
520             my ( $self, %piddles ) = @_;
521              
522             $self->new->_set_attr( %piddles );
523             }
524              
525              
526             my $obj = My::Class->new;
527              
528             # clone $obj and filter piddles.
529             my $new = $obj->where( $obj->p1 > 5 );
530              
531             =head1 DESCRIPTION
532              
533             B<MooX::PDL::Role::Proxy> is a L<Moo::Role> which turns its
534             consumer into a proxy object for some of its attributes, which are
535             assumed to be B<PDL> objects (or other proxy objects). A subset of
536             B<PDL> methods applied to the proxy object are applied to the selected
537             attributes. (See L<PDL::QuckStart> for more information on B<PDL> and
538             its objects (piddles)).
539              
540             As an example, consider an object representing a set of detected
541             events (think physics, not computing), which contains metadata
542             describing the events as well as piddles representing event position,
543             energy, and arrival time. The structure might look like this:
544              
545             {
546             metadata => \%metadata,
547             time => $time, # piddle
548             x => $x, # piddle
549             y => $y, # piddle
550             energy => $energy # piddle
551             }
552              
553             To filter the events on energy would traditionally be performed
554             explicitly on each element in the structure, e.g.
555              
556             my $mask = which( $obj->{energy} > 20 );
557              
558             my $copy = {};
559             $copy->{time} = $obj->{time}->where( $mask );
560             $copy->{x} = $obj->{x}->where( $mask );
561             $copy->{y} = $obj->{y}->where( $mask );
562             $copy->{energy} = $obj->{energy}->where( $mask );
563              
564             Or, more succinctly,
565              
566             $new->{$_} = $obj->{$_}->where( $mask ) for qw( time x y energy );
567              
568             With B<MooX::PDL::Role::Proxy> this turns into
569              
570             my $copy = $obj->where( $mask );
571              
572             Or, if the results should be stored in the same object,
573              
574             $obj->inplace->where( $mask );
575              
576             =head2 Usage and Class requirements
577              
578             Each attribute to be operated on by the common C<PDL>-like
579             operators should be given a C<piddle> option, e.g.
580              
581             has p1 => (
582             is => 'rw',
583             default => sub { sequence( 10 ) },
584             piddle => 1,
585             );
586              
587             (Treat the option value as an identifier for the group of piddles
588             which should be operated on, rather than as a boolean).
589              
590             To support non-inplace operations, the class must provide a
591             C<clone_with_piddles> method with the following signature:
592              
593             sub clone_with_piddles ( $self, %piddles )
594              
595             It should clone C<$self> and assign the values in C<%piddles>
596             to the attributes named by its keys. To assist with the latter
597             operation, see the provided L</_set_attrs> method.
598              
599             To support inplace operations, attributes tagged with the C<piddle>
600             option must have write accessors. They may be public or private.
601              
602             =head2 Nested Proxy Objects
603              
604             A class with the applied role should respond equivalently to a true
605             piddle when the supported methods are called on it (it's a bug
606             otherwise). Thus, it is possible for a proxy object to contain
607             another, and as long as the contained object has the C<piddle>
608             attribute set, the supported method will be applied to the
609             contained object appropriately.
610              
611             =head1 METHODS
612              
613             =head2 _piddles
614              
615             @piddle_names = $obj->_piddles;
616              
617             This returns a list of the names of the object's attributes with
618             a C<piddle> tag set. The list is lazily created by the C<_build__piddles>
619             method, which can be modified or overridden if required. The default
620             action is to find all tagged attributes with tag C<piddle>.
621              
622             =head2 _clear_piddles
623              
624             Clear the list of attributes which have been tagged as piddles. The
625             list will be reset to the defaults when C<_piddles> is next invoked.
626              
627             =head2 _apply_to_tagged_attrs
628              
629             $obj->_apply_to_tagged_attrs( \&sub );
630              
631             Execute the passed subroutine on all of the attributes tagged with the
632             C<piddle> option. The subroutine will be invoked as
633              
634             sub->( $attribute, $inplace )
635              
636             where C<$inplace> will be true if the operation is to take place inplace.
637              
638             The subroutine should return the piddle to be stored.
639              
640             Returns C<$obj> if applied in-place, or a new object if not.
641              
642             =head2 inplace
643              
644             $obj->inplace( ?$how )
645              
646             Indicate that the next I<inplace aware> operation should be done inplace.
647              
648             An optional argument indicating how the piddles should be updated may be
649             passed (see L</set_inplace> for more information). This API differs from
650             from the L<inplace|PDL::Core/inplace> method.
651              
652             It defaults to using the attributes' accessors to store the results,
653             which will cause triggers, etc. to be called.
654              
655             Returns C<$obj>.
656             See also L</inplace_direct> and L</inplace_accessor>.
657              
658             =head2 inplace_store
659              
660             $obj->inplace_store
661              
662             Indicate that the next I<inplace aware> operation should be done
663             inplace. Piddles are changed inplace via the C<.=> operator, avoiding
664             any side-effects caused by using the attributes' accessors.
665              
666             It is equivalent to calling
667              
668             $obj->set_inplace( MooX::PDL::Role::Proxy::INPLACE_STORE );
669              
670             Returns C<$obj>.
671             See also L</inplace> and L</inplace_accessor>.
672              
673             =head2 inplace_set
674              
675             $obj->inplace_set
676              
677             Indicate that the next I<inplace aware> operation should be done inplace.
678             The object level attribute accessors will be used to store the results (which
679             may be the same piddle). This will cause L<Moo> triggers, etc to be
680             called.
681              
682             It is equivalent to calling
683              
684             $obj->set_inplace( MooX::PDL::Role::Proxy::INPLACE_SET );
685              
686             Returns C<$obj>.
687             See also L</inplace_direct> and L</inplace>.
688              
689             =head2 set_inplace
690              
691             $obj->set_inplace( $value );
692              
693             Change the value of the inplace flag. Accepted values are
694              
695             =over
696              
697             =item MooX::PDL::Role::Proxy::INPLACE_SET
698              
699             Use the object level attribute accessors to store the results (which
700             may be the same piddle). This will cause L<Moo> triggers, etc to be
701             called.
702              
703             =item MooX::PDL::Role::Proxy::INPLACE_STORE
704              
705             Store the results directly in the existing piddle using the C<.=> operator.
706              
707             =back
708              
709             =head2 is_inplace
710              
711             $bool = $obj->is_inplace;
712              
713             Test if the next I<inplace aware> operation should be done inplace
714              
715             =head2 copy
716              
717             $new = $obj->copy;
718              
719             Create a copy of the object and its piddles. If the C<inplace> flag
720             is set, it returns C<$obj> otherwise it is exactly equivalent to
721              
722             $obj->clone_with_piddles( map { $_ => $obj->$_->copy } @{ $obj->_piddles } );
723              
724             =head2 sever
725              
726             $obj = $obj->sever;
727              
728             Call L<PDL::Core/sever> on tagged attributes. This is done inplace.
729             Returns C<$obj>.
730              
731             =head2 index
732              
733             $new = $obj->index( PIDDLE );
734              
735             Call L<PDL::Slices/index> on tagged attributes. This is inplace aware.
736             Returns C<$obj> if applied in-place, or a new object if not.
737              
738             =head2 at
739              
740             $obj = $obj->at( @indices );
741              
742             Returns a simple object containing the results of running
743             L<PDL::Core/index> on tagged attributes. The object's attributes are
744             named after the tagged attributes.
745              
746             =head2 where
747              
748             $obj = $obj->where( $mask );
749              
750             Apply L<PDL::Primitive/where> to the tagged attributes. It is in-place aware.
751             Returns C<$obj> if applied in-place, or a new object if not.
752              
753             =head2 _set_attr
754              
755             $obj->_set_attr( %attr )
756              
757             Set the object's attributes to the values in the C<%attr> hash.
758              
759             Returns C<$obj>.
760              
761             =head2 qsort
762              
763             $obj->qsort;
764              
765             Sort the piddles. This requires that the object has a C<qsorti> method, which should
766             return a piddle index of the elements in ascending order.
767              
768             For example, to designate the C<radius> attribute as that which should be sorted
769             on by qsort, include the C<handles> option when declaring it:
770              
771             has radius => (
772             is => 'ro',
773             piddle => 1,
774             isa => Piddle1D,
775             handles => ['qsorti'],
776             );
777              
778             It is in-place aware. Returns C<$obj> if applied in-place, or a new object if not.
779              
780             =head2 qsort_on
781              
782             $obj->sort_on( $piddle );
783              
784             Sort on the specified C<$piddle>.
785              
786             It is in-place aware.
787             Returns C<$obj> if applied in-place, or a new object if not.
788              
789             =head2 clip_on
790              
791             $obj->clip_on( $piddle, $min, $max );
792              
793             Clip on the specified C<$piddle>, removing elements which are outside
794             the bounds of [C<$min>, C<$max>). Either bound may be C<undef> to indicate
795             it should be ignore.
796              
797             It is in-place aware.
798              
799             Returns C<$obj> if applied in-place, or a new object if not.
800              
801             =head2 slice
802              
803             $obj->slice( $slice );
804              
805             Slice. See L<PDL::Slices/slice> for more information.
806              
807             It is in-place aware.
808             Returns C<$obj> if applied in-place, or a new object if not.
809              
810             =head1 LIMITATIONS
811              
812             There are significant limits to this encapsulation.
813              
814             =over
815              
816             =item *
817              
818             The piddles operated on must be similar enough in structure so that
819             the ganged operations make sense (and are valid!).
820              
821             =item *
822              
823             There is (currently) no way to indicate that there are different sets
824             of piddles contained within the object.
825              
826             =item *
827              
828             The object must be able to be cloned relatively easily, so that
829             non-inplace operations can create copies of the original object.
830              
831             =back
832              
833             =head1 SUPPORT
834              
835             =head2 Bugs
836              
837             Please report any bugs or feature requests to or through the web interface at: https://rt.cpan.org/Public/Dist/Display.html?Name=MooX-PDL-Role-Proxy
838              
839             =head2 Source
840              
841             Source is available at
842              
843             https://gitlab.com/djerius/moox-pdl-role-proxy
844              
845             and may be cloned from
846              
847             https://gitlab.com/djerius/moox-pdl-role-proxy.git
848              
849             =head1 AUTHOR
850              
851             Diab Jerius <djerius@cpan.org>
852              
853             =head1 COPYRIGHT AND LICENSE
854              
855             This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
856              
857             This is free software, licensed under:
858              
859             The GNU General Public License, Version 3, June 2007
860              
861             =cut