Add built local::lib
[catagits/Gitalist.git] / local-lib5 / lib / perl5 / Algorithm / C3.pm
CommitLineData
3fea05b9 1
2package Algorithm::C3;
3
4use strict;
5use warnings;
6
7use Carp 'confess';
8
9our $VERSION = '0.08';
10
11sub merge {
12 my ($root, $parent_fetcher, $cache) = @_;
13
14 $cache ||= {};
15
16 my @STACK; # stack for simulating recursion
17
18 my $pfetcher_is_coderef = ref($parent_fetcher) eq 'CODE';
19
20 unless ($pfetcher_is_coderef or $root->can($parent_fetcher)) {
21 confess "Could not find method $parent_fetcher in $root";
22 }
23
24 my $current_root = $root;
25 my $current_parents = [ $root->$parent_fetcher ];
26 my $recurse_mergeout = [];
27 my $i = 0;
28 my %seen = ( $root => 1 );
29
30 my ($new_root, $mergeout, %tails);
31 while(1) {
32 if($i < @$current_parents) {
33 $new_root = $current_parents->[$i++];
34
35 if($seen{$new_root}) {
36 my @isastack;
37 my $reached;
38 for(my $i = 0; $i < $#STACK; $i += 4) {
39 if($reached || ($reached = ($STACK[$i] eq $new_root))) {
40 push(@isastack, $STACK[$i]);
41 }
42 }
43 my $isastack = join(q{ -> }, @isastack, $current_root, $new_root);
44 die "Infinite loop detected in parents of '$root': $isastack";
45 }
46 $seen{$new_root} = 1;
47
48 unless ($pfetcher_is_coderef or $new_root->can($parent_fetcher)) {
49 confess "Could not find method $parent_fetcher in $new_root";
50 }
51
52 push(@STACK, $current_root, $current_parents, $recurse_mergeout, $i);
53
54 $current_root = $new_root;
55 $current_parents = $cache->{pfetch}->{$current_root} ||= [ $current_root->$parent_fetcher ];
56 $recurse_mergeout = [];
57 $i = 0;
58 next;
59 }
60
61 $seen{$current_root} = 0;
62
63 $mergeout = $cache->{merge}->{$current_root} ||= do {
64
65 # This do-block is the code formerly known as the function
66 # that was a perl-port of the python code at
67 # http://www.python.org/2.3/mro.html :)
68
69 # Initial set (make sure everything is copied - it will be modded)
70 my @seqs = map { [@$_] } @$recurse_mergeout;
71 push(@seqs, [@$current_parents]) if @$current_parents;
72
73 # Construct the tail-checking hash (actually, it's cheaper and still
74 # correct to re-use it throughout this function)
75 foreach my $seq (@seqs) {
76 $tails{$seq->[$_]}++ for (1..$#$seq);
77 }
78
79 my @res = ( $current_root );
80 while (1) {
81 my $cand;
82 my $winner;
83 foreach (@seqs) {
84 next if !@$_;
85 if(!$winner) { # looking for a winner
86 $cand = $_->[0]; # seq head is candidate
87 next if $tails{$cand}; # he loses if in %tails
88
89 # Handy warn to give a output like the ones on
90 # http://www.python.org/download/releases/2.3/mro/
91 #warn " = " . join(' + ', @res) . " + merge([" . join('] [', map { join(', ', @$_) } grep { @$_ } @seqs) . "])\n";
92 push @res => $winner = $cand;
93 shift @$_; # strip off our winner
94 $tails{$_->[0]}-- if @$_; # keep %tails sane
95 }
96 elsif($_->[0] eq $winner) {
97 shift @$_; # strip off our winner
98 $tails{$_->[0]}-- if @$_; # keep %tails sane
99 }
100 }
101
102 # Handy warn to give a output like the ones on
103 # http://www.python.org/download/releases/2.3/mro/
104 #warn " = " . join(' + ', @res) . "\n" if !$cand;
105
106 last if !$cand;
107 die q{Inconsistent hierarchy found while merging '}
108 . $current_root . qq{':\n\t}
109 . qq{current merge results [\n\t\t}
110 . (join ",\n\t\t" => @res)
111 . qq{\n\t]\n\t} . qq{merging failed on '$cand'\n}
112 if !$winner;
113 }
114 \@res;
115 };
116
117 return @$mergeout if !@STACK;
118
119 $i = pop(@STACK);
120 $recurse_mergeout = pop(@STACK);
121 $current_parents = pop(@STACK);
122 $current_root = pop(@STACK);
123
124 push(@$recurse_mergeout, $mergeout);
125 }
126}
127
1281;
129
130__END__
131
132=pod
133
134=head1 NAME
135
136Algorithm::C3 - A module for merging hierarchies using the C3 algorithm
137
138=head1 SYNOPSIS
139
140 use Algorithm::C3;
141
142 # merging a classic diamond
143 # inheritance graph like this:
144 #
145 # <A>
146 # / \
147 # <B> <C>
148 # \ /
149 # <D>
150
151 my @merged = Algorithm::C3::merge(
152 'D',
153 sub {
154 # extract the ISA array
155 # from the package
156 no strict 'refs';
157 @{$_[0] . '::ISA'};
158 }
159 );
160
161 print join ", " => @merged; # prints D, B, C, A
162
163=head1 DESCRIPTION
164
165This module implements the C3 algorithm. I have broken this out
166into it's own module because I found myself copying and pasting
167it way too often for various needs. Most of the uses I have for
168C3 revolve around class building and metamodels, but it could
169also be used for things like dependency resolution as well since
170it tends to do such a nice job of preserving local precedence
171orderings.
172
173Below is a brief explanation of C3 taken from the L<Class::C3>
174module. For more detailed information, see the L<SEE ALSO> section
175and the links there.
176
177=head2 What is C3?
178
179C3 is the name of an algorithm which aims to provide a sane method
180resolution order under multiple inheritance. It was first introduced
181in the language Dylan (see links in the L<SEE ALSO> section), and
182then later adopted as the preferred MRO (Method Resolution Order)
183for the new-style classes in Python 2.3. Most recently it has been
184adopted as the 'canonical' MRO for Perl 6 classes, and the default
185MRO for Parrot objects as well.
186
187=head2 How does C3 work.
188
189C3 works by always preserving local precedence ordering. This
190essentially means that no class will appear before any of it's
191subclasses. Take the classic diamond inheritance pattern for
192instance:
193
194 <A>
195 / \
196 <B> <C>
197 \ /
198 <D>
199
200The standard Perl 5 MRO would be (D, B, A, C). The result being that
201B<A> appears before B<C>, even though B<C> is the subclass of B<A>.
202The C3 MRO algorithm however, produces the following MRO (D, B, C, A),
203which does not have this same issue.
204
205This example is fairly trivial, for more complex examples and a deeper
206explanation, see the links in the L<SEE ALSO> section.
207
208=head1 FUNCTION
209
210=over 4
211
212=item B<merge ($root, $func_to_fetch_parent, $cache)>
213
214This takes a C<$root> node, which can be anything really it
215is up to you. Then it takes a C<$func_to_fetch_parent> which
216can be either a CODE reference (see L<SYNOPSIS> above for an
217example), or a string containing a method name to be called
218on all the items being linearized. An example of how this
219might look is below:
220
221 {
222 package A;
223
224 sub supers {
225 no strict 'refs';
226 @{$_[0] . '::ISA'};
227 }
228
229 package C;
230 our @ISA = ('A');
231 package B;
232 our @ISA = ('A');
233 package D;
234 our @ISA = ('B', 'C');
235 }
236
237 print join ", " => Algorithm::C3::merge('D', 'supers');
238
239The purpose of C<$func_to_fetch_parent> is to provide a way
240for C<merge> to extract the parents of C<$root>. This is
241needed for C3 to be able to do it's work.
242
243The C<$cache> parameter is an entirely optional performance
244measure, and should not change behavior.
245
246If supplied, it should be a hashref that merge can use as a
247private cache between runs to speed things up. Generally
248speaking, if you will be calling merge many times on related
249things, and the parent fetching function will return constant
250results given the same arguments during all of these calls,
251you can and should reuse the same shared cache hash for all
252of the calls. Example:
253
254 sub do_some_merging {
255 my %merge_cache;
256 my @foo_mro = Algorithm::C3::Merge('Foo', \&get_supers, \%merge_cache);
257 my @bar_mro = Algorithm::C3::Merge('Bar', \&get_supers, \%merge_cache);
258 my @baz_mro = Algorithm::C3::Merge('Baz', \&get_supers, \%merge_cache);
259 my @quux_mro = Algorithm::C3::Merge('Quux', \&get_supers, \%merge_cache);
260 # ...
261 }
262
263=back
264
265=head1 CODE COVERAGE
266
267I use B<Devel::Cover> to test the code coverage of my tests, below
268is the B<Devel::Cover> report on this module's test suite.
269
270 ------------------------ ------ ------ ------ ------ ------ ------ ------
271 File stmt bran cond sub pod time total
272 ------------------------ ------ ------ ------ ------ ------ ------ ------
273 Algorithm/C3.pm 100.0 100.0 100.0 100.0 100.0 100.0 100.0
274 ------------------------ ------ ------ ------ ------ ------ ------ ------
275 Total 100.0 100.0 100.0 100.0 100.0 100.0 100.0
276 ------------------------ ------ ------ ------ ------ ------ ------ ------
277
278=head1 SEE ALSO
279
280=head2 The original Dylan paper
281
282=over 4
283
284=item L<http://www.webcom.com/haahr/dylan/linearization-oopsla96.html>
285
286=back
287
288=head2 The prototype Perl 6 Object Model uses C3
289
290=over 4
291
292=item L<http://svn.openfoundry.org/pugs/perl5/Perl6-MetaModel/>
293
294=back
295
296=head2 Parrot now uses C3
297
298=over 4
299
300=item L<http://aspn.activestate.com/ASPN/Mail/Message/perl6-internals/2746631>
301
302=item L<http://use.perl.org/~autrijus/journal/25768>
303
304=back
305
306=head2 Python 2.3 MRO related links
307
308=over 4
309
310=item L<http://www.python.org/2.3/mro.html>
311
312=item L<http://www.python.org/2.2.2/descrintro.html#mro>
313
314=back
315
316=head2 C3 for TinyCLOS
317
318=over 4
319
320=item L<http://www.call-with-current-continuation.org/eggs/c3.html>
321
322=back
323
324=head1 AUTHORS
325
326Stevan Little, E<lt>stevan@iinteractive.comE<gt>
327
328Brandon L. Black, E<lt>blblack@gmail.comE<gt>
329
330=head1 COPYRIGHT AND LICENSE
331
332Copyright 2006 by Infinity Interactive, Inc.
333
334L<http://www.iinteractive.com>
335
336This library is free software; you can redistribute it and/or modify
337it under the same terms as Perl itself.
338
339=cut