branching off to work on performance
[gitmo/Algorithm-C3.git] / lib / Algorithm / C3.pm
CommitLineData
c0b91998 1
2package Algorithm::C3;
3
4use strict;
5use warnings;
6
7use Carp 'confess';
8
9our $VERSION = '0.01';
10
11# this function is a perl-port of the
12# python code on this page:
13# http://www.python.org/2.3/mro.html
14sub _merge {
15 my (@seqs) = @_;
16 my $class_being_merged = $seqs[0]->[0];
17 my @res;
18 while (1) {
19 # remove all empty seqences
20 my @nonemptyseqs = (map { (@{$_} ? $_ : ()) } @seqs);
21 # return the list if we have no more no-empty sequences
22 return @res if not @nonemptyseqs;
23 my $reject;
24 my $cand; # a canidate ..
25 foreach my $seq (@nonemptyseqs) {
26 $cand = $seq->[0]; # get the head of the list
27 my $nothead;
28 foreach my $sub_seq (@nonemptyseqs) {
29 # XXX - this is instead of the python "in"
30 my %in_tail = (map { $_ => 1 } @{$sub_seq}[ 1 .. $#{$sub_seq} ]);
31 # NOTE:
32 # jump out as soon as we find one matching
33 # there is no reason not too. However, if
34 # we find one, then just remove the '&& last'
35 ++$nothead && last if exists $in_tail{$cand};
36 }
37 last unless $nothead; # leave the loop with our canidate ...
38 $reject = $cand;
39 $cand = undef; # otherwise, reject it ...
40 }
41 die "Inconsistent hierarchy found while merging '$class_being_merged':\n\t" .
42 "current merge results [\n\t\t" . (join ",\n\t\t" => @res) . "\n\t]\n\t" .
8fe16bec 43 "merging failed on '$reject'\n" if not $cand;
c0b91998 44 push @res => $cand;
45 # now loop through our non-empties and pop
46 # off the head if it matches our canidate
47 foreach my $seq (@nonemptyseqs) {
48 shift @{$seq} if $seq->[0] eq $cand;
49 }
50 }
51}
52
53sub merge {
54 my ($root, $_parent_fetcher) = @_;
55 my $parent_fetcher = $_parent_fetcher;
56 unless (ref($parent_fetcher) && ref($parent_fetcher) eq 'CODE') {
57 $parent_fetcher = $root->can($_parent_fetcher) || confess "Could not find method $_parent_fetcher in $root";
58 }
59 return _merge(
60 [ $root ],
61 (map { [ merge($_, $_parent_fetcher) ] } $root->$parent_fetcher()),
62 [ $parent_fetcher->($root) ],
63 );
64}
65
661;
67
68__END__
69
70=pod
71
72=head1 NAME
73
8fe16bec 74Algorithm::C3 - A module for merging hierarchies using the C3 algorithm
c0b91998 75
76=head1 SYNOPSIS
77
78 use Algorithm::C3;
79
80 # merging a classic diamond
81 # inheritence graph like this:
82 #
83 # <A>
84 # / \
85 # <B> <C>
86 # \ /
87 # <D>
88
89 my @merged = Algorithm::C3::merge(
90 'D',
91 sub {
92 # extract the ISA array
93 # from the package
94 no strict 'refs';
95 @{$_[0] . '::ISA'};
96 }
97 );
98
99 print join ", " => @merged; # prints D, B, C, A
100
101=head1 DESCRIPTION
102
103This module implements the C3 algorithm. I have broken this out
104into it's own module because I found myself copying and pasting
105it way too often for various needs. Most of the uses I have for
106C3 revolve around class building and metamodels, but it could
107also be used for things like dependency resolution as well since
108it tends to do such a nice job of preserving local precendence
109orderings.
110
111Below is a brief explanation of C3 taken from the L<Class::C3>
112module. For more detailed information, see the L<SEE ALSO> section
113and the links there.
114
115=head2 What is C3?
116
117C3 is the name of an algorithm which aims to provide a sane method
118resolution order under multiple inheritence. It was first introduced
119in the langauge Dylan (see links in the L<SEE ALSO> section), and
120then later adopted as the prefered MRO (Method Resolution Order)
121for the new-style classes in Python 2.3. Most recently it has been
122adopted as the 'canonical' MRO for Perl 6 classes, and the default
123MRO for Parrot objects as well.
124
125=head2 How does C3 work.
126
127C3 works by always preserving local precendence ordering. This
128essentially means that no class will appear before any of it's
129subclasses. Take the classic diamond inheritence pattern for
130instance:
131
132 <A>
133 / \
134 <B> <C>
135 \ /
136 <D>
137
138The standard Perl 5 MRO would be (D, B, A, C). The result being that
139B<A> appears before B<C>, even though B<C> is the subclass of B<A>.
140The C3 MRO algorithm however, produces the following MRO (D, B, C, A),
141which does not have this same issue.
142
143This example is fairly trival, for more complex examples and a deeper
144explaination, see the links in the L<SEE ALSO> section.
145
146=head1 FUNCTION
147
148=over 4
149
150=item B<merge ($root, $func_to_fetch_parent)>
151
152This takes a C<$root> node, which can be anything really it
153is up to you. Then it takes a C<$func_to_fetch_parent> which
154can be either a CODE reference (see L<SYNOPSIS> above for an
155example), or a string containing a method name to be called
156on all the items being linearized. An example of how this
157might look is below:
158
159 {
160 package A;
161
162 sub supers {
163 no strict 'refs';
164 @{$_[0] . '::ISA'};
165 }
166
167 package C;
168 our @ISA = ('A');
169 package B;
170 our @ISA = ('A');
171 package D;
172 our @ISA = ('B', 'C');
173 }
174
175 print join ", " => Algorithm::C3::merge('D', 'supers');
176
177The purpose of C<$func_to_fetch_parent> is to provide a way
178for C<merge> to extract the parents of C<$root>. This is
179needed for C3 to be able to do it's work.
180
181=back
182
183=head1 CODE COVERAGE
184
185I use B<Devel::Cover> to test the code coverage of my tests, below
186is the B<Devel::Cover> report on this module's test suite.
187
188 ------------------------ ------ ------ ------ ------ ------ ------ ------
189 File stmt bran cond sub pod time total
190 ------------------------ ------ ------ ------ ------ ------ ------ ------
191 Algorithm/C3.pm 100.0 100.0 55.6 100.0 100.0 100.0 94.4
192 ------------------------ ------ ------ ------ ------ ------ ------ ------
193 Total 100.0 100.0 55.6 100.0 100.0 100.0 94.4
194 ------------------------ ------ ------ ------ ------ ------ ------ ------
195
196=head1 SEE ALSO
197
198=head2 The original Dylan paper
199
200=over 4
201
202=item L<http://www.webcom.com/haahr/dylan/linearization-oopsla96.html>
203
204=back
205
206=head2 The prototype Perl 6 Object Model uses C3
207
208=over 4
209
210=item L<http://svn.openfoundry.org/pugs/perl5/Perl6-MetaModel/>
211
212=back
213
214=head2 Parrot now uses C3
215
216=over 4
217
218=item L<http://aspn.activestate.com/ASPN/Mail/Message/perl6-internals/2746631>
219
220=item L<http://use.perl.org/~autrijus/journal/25768>
221
222=back
223
224=head2 Python 2.3 MRO related links
225
226=over 4
227
228=item L<http://www.python.org/2.3/mro.html>
229
230=item L<http://www.python.org/2.2.2/descrintro.html#mro>
231
232=back
233
234=head2 C3 for TinyCLOS
235
236=over 4
237
238=item L<http://www.call-with-current-continuation.org/eggs/c3.html>
239
240=back
241
242=head1 AUTHOR
243
244Stevan Little, E<lt>stevan@iinteractive.comE<gt>
245
246=head1 COPYRIGHT AND LICENSE
247
248Copyright 2006 by Infinity Interactive, Inc.
249
250L<http://www.iinteractive.com>
251
252This library is free software; you can redistribute it and/or modify
253it under the same terms as Perl itself.
254
255=cut