cbd74d2d6ac041091ff9d1005ad4207142ef21d1
[gitmo/Algorithm-C3.git] / lib / Algorithm / C3.pm
1
2 package Algorithm::C3;
3
4 use strict;
5 use warnings;
6
7 use Carp 'confess';
8
9 our $VERSION = '0.09';
10
11 sub 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
128 1;
129
130 __END__
131
132 =pod
133
134 =head1 NAME
135
136 Algorithm::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
165 This module implements the C3 algorithm. I have broken this out
166 into it's own module because I found myself copying and pasting
167 it way too often for various needs. Most of the uses I have for
168 C3 revolve around class building and metamodels, but it could
169 also be used for things like dependency resolution as well since
170 it tends to do such a nice job of preserving local precedence
171 orderings.
172
173 Below is a brief explanation of C3 taken from the L<Class::C3>
174 module. For more detailed information, see the L<SEE ALSO> section
175 and the links there.
176
177 =head2 What is C3?
178
179 C3 is the name of an algorithm which aims to provide a sane method
180 resolution order under multiple inheritance. It was first introduced
181 in the language Dylan (see links in the L<SEE ALSO> section), and
182 then later adopted as the preferred MRO (Method Resolution Order)
183 for the new-style classes in Python 2.3. Most recently it has been
184 adopted as the 'canonical' MRO for Perl 6 classes, and the default
185 MRO for Parrot objects as well.
186
187 =head2 How does C3 work.
188
189 C3 works by always preserving local precedence ordering. This
190 essentially means that no class will appear before any of it's
191 subclasses. Take the classic diamond inheritance pattern for
192 instance:
193
194      <A>
195     /   \
196   <B>   <C>
197     \   /
198      <D>
199
200 The standard Perl 5 MRO would be (D, B, A, C). The result being that
201 B<A> appears before B<C>, even though B<C> is the subclass of B<A>.
202 The C3 MRO algorithm however, produces the following MRO (D, B, C, A),
203 which does not have this same issue.
204
205 This example is fairly trivial, for more complex examples and a deeper
206 explanation, 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
214 This takes a C<$root> node, which can be anything really it
215 is up to you. Then it takes a C<$func_to_fetch_parent> which
216 can be either a CODE reference (see L<SYNOPSIS> above for an
217 example), or a string containing a method name to be called
218 on all the items being linearized. An example of how this
219 might 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
239 The purpose of C<$func_to_fetch_parent> is to provide a way
240 for C<merge> to extract the parents of C<$root>. This is
241 needed for C3 to be able to do it's work.
242
243 The C<$cache> parameter is an entirely optional performance
244 measure, and should not change behavior.
245
246 If supplied, it should be a hashref that merge can use as a
247 private cache between runs to speed things up.  Generally
248 speaking, if you will be calling merge many times on related
249 things, and the parent fetching function will return constant
250 results given the same arguments during all of these calls,
251 you can and should reuse the same shared cache hash for all
252 of 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
267 I use B<Devel::Cover> to test the code coverage of my tests, below
268 is 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
326 Stevan Little, E<lt>stevan@iinteractive.comE<gt>
327
328 Brandon L. Black, E<lt>blblack@gmail.comE<gt>
329
330 =head1 COPYRIGHT AND LICENSE
331
332 Copyright 2006 by Infinity Interactive, Inc.
333
334 L<http://www.iinteractive.com>
335
336 This library is free software; you can redistribute it and/or modify
337 it under the same terms as Perl itself.
338
339 =cut