File Coverage

blib/lib/Forest/Tree/Pure.pm
Criterion Covered Total %
statement 105 118 88.9
branch 23 40 57.5
condition 5 15 33.3
subroutine 22 26 84.6
pod 18 18 100.0
total 173 217 79.7


line stmt bran cond sub pod time code
1             package Forest::Tree::Pure;
2 16     16   122470 use Moose;
  16         1318574  
  16         125  
3              
4             our $VERSION = '0.10';
5             our $AUTHORITY = 'cpan:STEVAN';
6              
7 16     16   139452 use Scalar::Util 'reftype', 'refaddr';
  16         45  
  16         1382  
8 16     16   103 use List::Util 'sum', 'max';
  16         33  
  16         53372  
9              
10             with qw(MooseX::Clone);
11              
12             has 'node' => (
13             is => 'ro',
14             isa => 'Item',
15             predicate => 'has_node',
16             );
17              
18             has 'uid' => (
19             is => 'rw',
20             isa => 'Value',
21             lazy => 1,
22             default => sub { (overload::StrVal($_[0]) =~ /\((.*?)\)$/)[0] },
23             );
24              
25             has 'children' => (
26             traits => ['Array'],
27             is => 'ro',
28             isa => 'ArrayRef[Forest::Tree::Pure]',
29             lazy => 1,
30             default => sub { [] },
31             handles => {
32             get_child_at => 'get',
33             child_count => 'count',
34             },
35             );
36              
37             has 'size' => (
38             traits => [qw(NoClone)],
39             is => 'ro',
40             isa => 'Int',
41             lazy_build => 1,
42             );
43              
44             sub _build_size {
45 44     44   70 my $self = shift;
46              
47 44 100       99 if ( $self->is_leaf ) {
48 18         1131 return 1;
49             } else {
50 26         47 return 1 + sum map { $_->size } @{ $self->children };
  44         1591  
  26         970  
51             }
52             }
53              
54             has 'height' => (
55             traits => [qw(NoClone)],
56             is => 'ro',
57             isa => 'Int',
58             lazy_build => 1,
59             );
60              
61             sub _build_height {
62 44     44   79 my $self = shift;
63              
64 44 100       116 if ( $self->is_leaf ) {
65 18         2863 return 0;
66             } else {
67 26         56 return 1 + max map { $_->height } @{ $self->children };
  44         1532  
  26         890  
68             }
69             }
70              
71             ## informational
72 114     114 1 19191 sub is_leaf { (shift)->child_count == 0 }
73              
74             ## traversal
75             sub traverse {
76 6     6 1 693 my ($self, @args) = @_;
77              
78 6         12 $_->visit(@args) for @{ $self->children };
  6         201  
79             }
80              
81             sub visit {
82 13     13 1 28 my ( $self, $f, @args ) = @_;
83              
84             $self->fmap_cont(sub {
85 50     50   157 my ( $tree, $cont, @args ) = @_;
86 50         182 $tree->$f(@args);
87 50         131 $cont->();
88 13         100 });
89             }
90              
91             sub fmap_cont {
92 207     207 1 845 my ( $self, @args ) = @_;
93              
94 207 100       985 unshift @args, "callback" if @args % 2 == 1;
95              
96 207         1457 my %args = ( depth => 0, path => [], index_path => [], @args );
97              
98 207         396 my $f = $args{callback};
99              
100 207 50       475 (defined($f))
101             || confess "Cannot traverse without traversal function";
102 207 50 33     1595 (!ref($f) or reftype($f) eq "CODE")
103             || confess "Traversal function must be a CODE reference or method name, not: $f";
104              
105             $self->$f(
106             sub {
107 182     182   299 my ( @inner_args ) = @_;
108 182 50       1373 unshift @inner_args, "callback" if @inner_args % 2 == 1;
109 182   33     8114 my $children = $args{children} || $self->children;
110              
111 182         604 my %child_args = ( %args, depth => $args{depth} + 1, path => [ @{ $args{path} }, $self ], parent => $self, @inner_args );
  182         1196  
112              
113 182         319 my @index_path = @{ $args{index_path} };
  182         684  
114              
115 182         397 my $i = 0;
116 177         261 map {
117 182         2023 my $index = $i++;
118 177         1547 $_->fmap_cont(
119             %child_args,
120             index => $index,
121             index_path => [ @index_path, $index ],
122             )
123             } @$children;
124             },
125 207         2006 %args,
126             );
127             }
128              
129             sub locate {
130 6     6 1 19 my ( $self, @path ) = @_;
131              
132 6         30 my @nodes = $self->descend(@path);
133              
134 6         141 return $nodes[-1];
135             }
136              
137             sub descend {
138 20     20 1 47 my ( $self, @path ) = @_;
139              
140 20 100       41 if ( @path ) {
141 14         27 my ( $head, @tail ) = @path;
142              
143 14 50       700 if ( my $child = $self->get_child_at($head) ) {
144 14         54 return ( $self, $child->descend(@tail) );
145             } else {
146 0         0 confess "No such child $head";
147             }
148             } else {
149 6         34 return $self;
150             }
151             }
152              
153             sub transform {
154 16     16 1 58 my ( $self, $path, $method, @args ) = @_;
155              
156 16 100       42 if ( @$path ) {
157 10         19 my ( $i, @path ) = @$path;
158              
159 10         571 my $targ = $self->get_child_at($i);
160              
161 10         44 my $transformed = $targ->transform(\@path, $method, @args);
162              
163 10 100       10500 if ( refaddr($transformed) == refaddr($targ) ) {
164 4         13 return $self;
165             } else {
166 6         27 return $self->set_child_at( $i => $transformed );
167             }
168             } else {
169 6         34 return $self->$method(@args);
170             }
171             }
172              
173             sub set_node {
174 0     0 1 0 my ( $self, $node ) = @_;
175              
176 0         0 $self->clone( node => $node );
177             }
178              
179             sub replace {
180 1     1 1 3 my ( $self, $replacement ) = @_;
181              
182 1         10 return $replacement;
183             }
184              
185             sub add_children {
186 4     4 1 14 my ( $self, @additional_children ) = @_;
187              
188 4         9 foreach my $child ( @additional_children ) {
189 4 0 33     145 (blessed($child) && $child->isa(ref $self))
    50          
190             || confess "Child parameter must be a " . ref($self) . " not (" . (defined $child ? $child : 'undef') . ")";
191             }
192              
193 4         11 my @children = @{ $self->children };
  4         161  
194              
195 4         11 push @children, @additional_children;
196              
197 4         31 return $self->clone( children => \@children );
198             }
199              
200             sub add_child {
201 4     4 1 397 my ( $self, $child ) = @_;
202              
203 4         18 $self->add_children($child);
204             }
205              
206             sub set_child_at {
207 6     6 1 3656 my ( $self, $index, $child ) = @_;
208              
209 6 0 33     73 (blessed($child) && $child->isa(ref $self))
    50          
210             || confess "Child parameter must be a " . ref($self) . " not (" . (defined $child ? $child : 'undef') . ")";
211              
212 6         17 my @children = @{ $self->children };
  6         221  
213              
214 6         13 $children[$index] = $child;
215              
216 6         30 $self->clone( children => \@children );
217             }
218              
219             sub remove_child_at {
220 2     2 1 5 my ( $self, $index ) = @_;
221              
222 2         4 my @children = @{ $self->children };
  2         129  
223              
224 2 50       8 confess "No child at index '$index'" if @children <= $index;
225              
226 2         6 splice @children, $index, 1;
227              
228 2         15 $self->clone( children => \@children );
229              
230             }
231              
232             sub insert_child_at {
233 3     3 1 8 my ( $self, $index, $child ) = @_;
234              
235 3 0 33     40 (blessed($child) && $child->isa('Forest::Tree::Pure'))
    50          
236             || confess "Child parameter must be a Forest::Tree::Pure not (" . (defined $child ? $child : 'undef') . ")";
237              
238 3         8 my @children = @{ $self->children };
  3         147  
239              
240 3 50       11 confess "'$index' is out of bounds" if @children < $index;
241              
242 3         9 splice @children, $index, 0, $child;
243              
244 3         16 $self->clone( children => \@children );
245             }
246              
247             sub get_child_index {
248 23     23 1 48 my ( $self, $child ) = @_;
249              
250 23         35 my $index = 0;
251 23         29 foreach my $sibling (@{ $self->children }) {
  23         768  
252 37 100       246 (refaddr($sibling) eq refaddr($child)) && return $index;
253 14         20 $index++;
254             }
255              
256 0           return;
257             }
258              
259             sub reconstruct_with_class {
260 0     0 1   my ( $self, $class ) = @_;
261              
262 0 0         confess "No class provided" unless defined($class);
263              
264 0           return $class->new(
265             node => $self->node,
266             children => [
267 0           map { $_->reconstruct_with_class($class) } @{ $self->children },
  0            
268             ],
269             );
270             }
271              
272             sub to_pure_tree {
273 0     0 1   my $self = shift;
274              
275 0           return $self;
276             }
277              
278             sub to_mutable_tree {
279 0     0 1   my $self = shift;
280              
281 0           $self->reconstruct_with_class("Forest::Tree");
282             }
283              
284             __PACKAGE__->meta->make_immutable;
285              
286 16     16   164 no Moose; 1;
  16         47  
  16         152  
287              
288             __END__
289              
290             =pod
291              
292             =head1 NAME
293              
294             Forest::Tree::Pure - An n-ary tree
295              
296             =head1 SYNOPSIS
297              
298             use Forest::Tree;
299              
300             my $t = Forest::Tree::Pure->new(
301             node => 1,
302             children => [
303             Forest::Tree::Pure->new(
304             node => 1.1,
305             children => [
306             Forest::Tree::Pure->new(node => 1.1.1),
307             Forest::Tree::Pure->new(node => 1.1.2),
308             Forest::Tree::Pure->new(node => 1.1.3),
309             ]
310             ),
311             Forest::Tree::Pure->new(node => 1.2),
312             Forest::Tree::Pure->new(
313             node => 1.3,
314             children => [
315             Forest::Tree::Pure->new(node => 1.3.1),
316             Forest::Tree::Pure->new(node => 1.3.2),
317             ]
318             ),
319             ]
320             );
321              
322             $t->traverse(sub {
323             my $t = shift;
324             print((' ' x $t->depth) . ($t->node || '\undef') . "\n");
325             });
326              
327             =head1 DESCRIPTION
328              
329             This module is a base class for L<Forest::Tree> providing functionality for
330             immutable trees.
331              
332             It can be used independently for trees that require sharing of children between
333             parents.
334              
335             There is no single authoritative parent (no upward links at all), and changing
336             of data is not supported.
337              
338             This class is appropriate when many tree roots share the same children (e.g. in
339             a versioned tree).
340              
341             This class is strictly a DAG, wheras L<Forest::Tree> produces a graph with back references
342              
343             =head1 ATTRIBUTES
344              
345             =over 4
346              
347             =item I<node>
348              
349             =item I<children>
350              
351             =over 4
352              
353             =item B<get_child_at ($index)>
354              
355             Return the child at this position. (zero-base index)
356              
357             =item B<child_count>
358              
359             Returns the number of children this tree has
360              
361             =back
362              
363             =item I<size>
364              
365             =over 4
366              
367             =item B<size>
368              
369             =item B<has_size>
370              
371             =back
372              
373             =item I<height>
374              
375             =over 4
376              
377             =item B<height>
378              
379             =item B<has_height>
380              
381             =back
382              
383             =back
384              
385             =head1 METHODS
386              
387             =over 4
388              
389             =item B<is_leaf>
390              
391             True if the current tree has no children
392              
393             =item B<traverse (\&func)>
394              
395             Takes a reference to a subroutine and traverses the tree applying this subroutine to
396             every descendant. (But not the root)
397              
398             =item B<visit (&func)>
399              
400             Traverse the entire tree, including the root.
401              
402             =item B<fmap_cont (&func)>
403              
404             A CPS form of C<visit> that lets you control when and how data flows from the children.
405              
406             It takes a callback in the form:
407              
408             sub {
409             my ( $tree, $cont, @args ) = @_;
410              
411             ...
412             }
413              
414             and C<$cont> is a code ref that when invoked will apply that same function to the children of C<$tree>.
415              
416             This allows you to do things like computing the sum of all the node values in a tree, for instance:
417              
418             use List::Util qw(sum);
419              
420             my $sum = $tree->fmap_cont(sub {
421             my ( $tree, $cont ) = @_;
422              
423             return sum( $tree->node, $cont->() );
424             });
425              
426             And also allows to stop traversal at a given point.
427              
428             =item B<add_children (@children)>
429              
430             =item B<add_child ($child)>
431              
432             Create a new tree node with the children appended.
433              
434             The children must inherit C<Forest::Tree::Pure>
435              
436             Note that this method does B<not> mutate the tree, instead it clones and
437             returns a tree with the augmented list of children.
438              
439             =item B<insert_child_at ($index, $child)>
440              
441             Insert a child at this position. (zero-base index)
442              
443             Returns a derived tree with overridden children.
444              
445             =item B<set_child_at ($index, $child)>
446              
447             Replaces the child at C<$index> with C<$child>.
448              
449             =item B<remove_child_at ($index)>
450              
451             Remove the child at this position. (zero-base index)
452              
453             Returns a derived tree with overridden children.
454              
455             =item B<locate (@path)>
456              
457             Find a child using a path of child indexes. These two examples return the same object:
458              
459             $tree->get_child_at(0)->get_child_at(1)->get_child_at(0);
460              
461             $tree->locate(0, 1, 0);
462              
463             =item B<descend (@path)>
464              
465             Like C<lookup> except that it returns every object in the path, not just the leaf.
466              
467             =item C<transform (\@path, $method, @args)>
468              
469             Performs a lookup on C<@path>, applies the method C<$method> with C<@args> to
470             the located node, and clones the path to the parent returning a derived tree.
471              
472             This method is also implemented in L<Forest::Tree> by mutating the tree in
473             place and returning the original tree, so the same transformations should work
474             on both pure trees and mutable ones.
475              
476             This code:
477              
478             my $new = $root->transform([ 1, 3 ], insert_child_at => 3, $new_child);
479              
480             will locate the child at the path C<[ 1, 3 ]>, call C<insert_child_at> on it,
481             creating a new version of C<[ 1, 3 ]>, and then return a cloned version of
482             C<[ 1 ]> and the root node recursively, such that C<$new> appears to be a
483             mutated C<$root>.
484              
485             =item set_node $new
486              
487             Returns a clone of the tree node with the node value changed.
488              
489             =item C<replace $arg>
490              
491             Returns the argument. This is useful when used with C<transform>.
492              
493             =item B<clone>
494              
495             Provided by L<MooseX::Clone>.
496              
497             Deeply clones the entire tree.
498              
499             Subclasses should use L<MooseX::Clone> traits to specify the correct cloning
500             behavior for additional attributes if cloning is used.
501              
502             =item B<reconstruct_with_class $class>
503              
504             Recursively recreates the tree by passing constructor arguments to C<$class>.
505              
506             Does not use C<clone>.
507              
508             =item B<to_mutable_tree>
509              
510             Invokes C<reconstruct_with_class> with L<Forest::Tree> as the argument.
511              
512             =item B<to_pure_tree>
513              
514             Returns the invocant.
515              
516             =item B<get_child_index ($child)>
517              
518             Returns the index of C<$child> in C<children> or undef if it isn't a child of
519             the current tree.
520              
521             =back
522              
523             =head1 BUGS
524              
525             All complex software has bugs lurking in it, and this module is no
526             exception. If you find a bug please either email me, or add the bug
527             to cpan-RT.
528              
529             =head1 AUTHOR
530              
531             Yuval Kogman
532              
533             =head1 COPYRIGHT AND LICENSE
534              
535             Copyright 2008-2014 Infinity Interactive, Inc.
536              
537             L<http://www.iinteractive.com>
538              
539             This library is free software; you can redistribute it and/or modify
540             it under the same terms as Perl itself.
541              
542             =cut