X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FClass%2FC3.pm;h=70eb5e77bb5947849d12daa94225427fdc9f390e;hb=6398c84857df49a84aa2c11f33f79381445f41df;hp=70dd4c146414050e31cf3ed4a5dd16093b370563;hpb=4a840d417528f1322644d5c11434787414744512;p=gitmo%2FClass-C3.git diff --git a/lib/Class/C3.pm b/lib/Class/C3.pm index 70dd4c1..70eb5e7 100644 --- a/lib/Class/C3.pm +++ b/lib/Class/C3.pm @@ -4,7 +4,7 @@ package Class::C3; use strict; use warnings; -our $VERSION = '0.20'; +our $VERSION = '0.29'; our $C3_IN_CORE; our $C3_XS; @@ -14,27 +14,37 @@ BEGIN { $C3_IN_CORE = 1; require mro; } - else { - eval "require Class::C3::XS"; - my $error = $@; - if(!$error) { - $C3_XS = 1; - } - else { + elsif($C3_XS or not defined $C3_XS) { + my $error = do { + local $@; + eval { require Class::C3::XS }; + $@; + }; + + if ($error) { die $error if $error !~ /\blocate\b/; + + if ($C3_XS) { + require Carp; + Carp::croak( "XS explicitly requested but Class::C3::XS is not available" ); + } + require Algorithm::C3; require Class::C3::next; } + else { + $C3_XS = 1; + } } } -# this is our global stash of both +# this is our global stash of both # MRO's and method dispatch tables # the structure basically looks like # this: # # $MRO{$class} = { -# MRO => [ ], +# MRO => [ ], # methods => { # orig => , # code => \& @@ -60,7 +70,7 @@ sub import { return if $TURN_OFF_C3; mro::set_mro($class, 'c3') if $C3_IN_CORE; - # make a note to calculate $class + # make a note to calculate $class # during INIT phase $MRO{$class} = undef unless exists $MRO{$class}; } @@ -93,12 +103,12 @@ sub initialize { sub uninitialize { # why bother if we don't have anything ... %next::METHOD_CACHE = (); - return unless keys %MRO; + return unless keys %MRO; if($C3_IN_CORE) { mro::set_mro($_, 'dfs') for keys %MRO; } else { - _remove_method_dispatch_tables(); + _remove_method_dispatch_tables(); $_initialized = 0; } } @@ -125,35 +135,35 @@ sub _calculate_method_dispatch_table { $MRO{$class} = { MRO => \@MRO }; my $has_overload_fallback; my %methods; - # NOTE: + # NOTE: # we do @MRO[1 .. $#MRO] here because it - # makes no sense to interogate the class - # which you are calculating for. + # makes no sense to interrogate the class + # which you are calculating for. foreach my $local (@MRO[1 .. $#MRO]) { - # if overload has tagged this module to + # if overload has tagged this module to # have use "fallback", then we want to - # grab that value - $has_overload_fallback = ${"${local}::()"} + # grab that value + $has_overload_fallback = ${"${local}::()"} if !defined $has_overload_fallback && defined ${"${local}::()"}; foreach my $method (grep { defined &{"${local}::$_"} } keys %{"${local}::"}) { - # skip if already overriden in local class + # skip if already overridden in local class next unless !defined *{"${class}::$method"}{CODE}; $methods{$method} = { orig => "${local}::$method", code => \&{"${local}::$method"} } unless exists $methods{$method}; } - } + } # now stash them in our %MRO table - $MRO{$class}->{methods} = \%methods; - $MRO{$class}->{has_overload_fallback} = $has_overload_fallback; + $MRO{$class}->{methods} = \%methods; + $MRO{$class}->{has_overload_fallback} = $has_overload_fallback; } sub _apply_method_dispatch_tables { return if $C3_IN_CORE; foreach my $class (keys %MRO) { _apply_method_dispatch_table($class); - } + } } sub _apply_method_dispatch_table { @@ -169,7 +179,7 @@ sub _apply_method_dispatch_table { ${"${class}::$method"} = $$orig if defined $$orig; } *{"${class}::$method"} = $MRO{$class}->{methods}->{$method}->{code}; - } + } } sub _remove_method_dispatch_tables { @@ -183,19 +193,19 @@ sub _remove_method_dispatch_table { return if $C3_IN_CORE; my $class = shift; no strict 'refs'; - delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback}; + delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback}; foreach my $method (keys %{$MRO{$class}->{methods}}) { delete ${"${class}::"}{$method} - if defined *{"${class}::${method}"}{CODE} && - (*{"${class}::${method}"}{CODE} eq $MRO{$class}->{methods}->{$method}->{code}); + if defined *{"${class}::${method}"}{CODE} && + (*{"${class}::${method}"}{CODE} eq $MRO{$class}->{methods}->{$method}->{code}); } } sub calculateMRO { my ($class, $merge_cache) = @_; - return Algorithm::C3::merge($class, sub { - no strict 'refs'; + return Algorithm::C3::merge($class, sub { + no strict 'refs'; @{$_[0] . '::ISA'}; }, $merge_cache); } @@ -223,27 +233,28 @@ __END__ =head1 NAME -Class::C3 - A pragma to use the C3 method resolution order algortihm +Class::C3 - A pragma to use the C3 method resolution order algorithm =head1 SYNOPSIS - package A; - use Class::C3; + # NOTE - DO NOT USE Class::C3 directly as a user, use MRO::Compat instead! + package ClassA; + use Class::C3; sub hello { 'A::hello' } - package B; - use base 'A'; - use Class::C3; + package ClassB; + use base 'ClassA'; + use Class::C3; - package C; - use base 'A'; - use Class::C3; + package ClassC; + use base 'ClassA'; + use Class::C3; sub hello { 'C::hello' } - package D; - use base ('B', 'C'); - use Class::C3; + package ClassD; + use base ('ClassB', 'ClassC'); + use Class::C3; # Classic Diamond MI pattern # @@ -253,35 +264,40 @@ Class::C3 - A pragma to use the C3 method resolution order algortihm # package main; - - # initializez the C3 module + + # initializez the C3 module # (formerly called in INIT) - Class::C3::initialize(); + Class::C3::initialize(); + + print join ', ' => Class::C3::calculateMRO('ClassD'); # prints ClassD, ClassB, ClassC, ClassA - print join ', ' => Class::C3::calculateMRO('Diamond_D') # prints D, B, C, A + print ClassD->hello(); # prints 'C::hello' instead of the standard p5 'A::hello' - print D->hello() # prints 'C::hello' instead of the standard p5 'A::hello' - - D->can('hello')->(); # can() also works correctly - UNIVERSAL::can('D', 'hello'); # as does UNIVERSAL::can() + ClassD->can('hello')->(); # can() also works correctly + UNIVERSAL::can('ClassD', 'hello'); # as does UNIVERSAL::can() =head1 DESCRIPTION -This is pragma to change Perl 5's standard method resolution order from depth-first left-to-right -(a.k.a - pre-order) to the more sophisticated C3 method resolution order. +This is pragma to change Perl 5's standard method resolution order from depth-first left-to-right +(a.k.a - pre-order) to the more sophisticated C3 method resolution order. + +B YOU SHOULD NOT USE THIS MODULE DIRECTLY - The feature provided +is integrated into perl version >= 5.9.5, and you should use L +instead, which will use the core implementation in newer perls, but fallback +to using this implementation on older perls. =head2 What is C3? C3 is the name of an algorithm which aims to provide a sane method resolution order under multiple -inheritence. It was first introduced in the langauge Dylan (see links in the L section), -and then later adopted as the prefered MRO (Method Resolution Order) for the new-style classes in -Python 2.3. Most recently it has been adopted as the 'canonical' MRO for Perl 6 classes, and the +inheritance. It was first introduced in the language Dylan (see links in the L section), +and then later adopted as the preferred MRO (Method Resolution Order) for the new-style classes in +Python 2.3. Most recently it has been adopted as the 'canonical' MRO for Perl 6 classes, and the default MRO for Parrot objects as well. =head2 How does C3 work. -C3 works by always preserving local precendence ordering. This essentially means that no class will -appear before any of it's subclasses. Take the classic diamond inheritence pattern for instance: +C3 works by always preserving local precedence ordering. This essentially means that no class will +appear before any of its subclasses. Take the classic diamond inheritance pattern for instance: / \ @@ -289,20 +305,20 @@ appear before any of it's subclasses. Take the classic diamond inheritence patte \ / -The standard Perl 5 MRO would be (D, B, A, C). The result being that B appears before B, even -though B is the subclass of B. The C3 MRO algorithm however, produces the following MRO +The standard Perl 5 MRO would be (D, B, A, C). The result being that B appears before B, even +though B is the subclass of B. The C3 MRO algorithm however, produces the following MRO (D, B, C, A), which does not have this same issue. -This example is fairly trival, for more complex examples and a deeper explaination, see the links in +This example is fairly trivial, for more complex examples and a deeper explanation, see the links in the L section. =head2 How does this module work? -This module uses a technique similar to Perl 5's method caching. When C is -called, this module calculates the MRO of all the classes which called C. It then -gathers information from the symbol tables of each of those classes, and builds a set of method -aliases for the correct dispatch ordering. Once all these C3-based method tables are created, it -then adds the method aliases into the local classes symbol table. +This module uses a technique similar to Perl 5's method caching. When C is +called, this module calculates the MRO of all the classes which called C. It then +gathers information from the symbol tables of each of those classes, and builds a set of method +aliases for the correct dispatch ordering. Once all these C3-based method tables are created, it +then adds the method aliases into the local classes symbol table. The end result is actually classes with pre-cached method dispatch. However, this caching does not do well if you start changing your C<@ISA> or messing with class symbol tables, so you should consider @@ -310,19 +326,19 @@ your classes to be effectively closed. See the L section for more detai =head1 OPTIONAL LOWERCASE PRAGMA -This release also includes an optional module B in the F folder. I did not include this in +This release also includes an optional module B in the F folder. I did not include this in the regular install since lowercase module names are considered I<"bad"> by some people. However I think that code looks much nicer like this: package MyClass; use c3; - -The the more clunky: + +This is more clunky: package MyClass; use Class::C3; - -But hey, it's your choice, thats why it is optional. + +But hey, it's your choice, that's why it is optional. =head1 FUNCTIONS @@ -334,31 +350,31 @@ Given a C<$class> this will return an array of class names in the proper C3 meth =item B -This B to initalize the C3 method dispatch tables, this module B if -you do not do this. It is advised to do this as soon as possible B loading any classes which +This B to initialize the C3 method dispatch tables, this module B if +you do not do this. It is advised to do this as soon as possible B loading any classes which use C3. Here is a quick code example: - + package Foo; use Class::C3; # ... Foo methods here - + package Bar; use Class::C3; use base 'Foo'; # ... Bar methods here - + package main; - + Class::C3::initialize(); # now it is safe to use Foo and Bar -This function used to be called automatically for you in the INIT phase of the perl compiler, but -that lead to warnings if this module was required at runtime. After discussion with my user base -(the L folks), we decided that calling this in INIT was more of an annoyance than a -convience. I apologize to anyone this causes problems for (although i would very suprised if I had -any other users other than the L folks). The simplest solution of course is to define -your own INIT method which calls this function. +This function used to be called automatically for you in the INIT phase of the perl compiler, but +that lead to warnings if this module was required at runtime. After discussion with my user base +(the L folks), we decided that calling this in INIT was more of an annoyance than a +convenience. I apologize to anyone this causes problems for (although I would be very surprised if I had +any other users other than the L folks). The simplest solution of course is to define +your own INIT method which calls this function. -NOTE: +NOTE: If C detects that C has already been executed, it will L and clear the MRO cache first. @@ -366,7 +382,7 @@ clear the MRO cache first. =item B Calling this function results in the removal of all cached methods, and the restoration of the old Perl 5 -style dispatch order (depth-first, left-to-right). +style dispatch order (depth-first, left-to-right). =item B @@ -376,51 +392,51 @@ This is an alias for L above. =head1 METHOD REDISPATCHING -It is always useful to be able to re-dispatch your method call to the "next most applicable method". This -module provides a pseudo package along the lines of C or C which will re-dispatch the -method along the C3 linearization. This is best show with an examples. +It is always useful to be able to re-dispatch your method call to the "next most applicable method". This +module provides a pseudo package along the lines of C or C which will re-dispatch the +method along the C3 linearization. This is best shown with an example. # a classic diamond MI pattern ... - - / \ - - \ / - - + # + # / \ + # + # \ / + # + package A; - use c3; - sub foo { 'A::foo' } - - package B; - use base 'A'; - use c3; - sub foo { 'B::foo => ' . (shift)->next::method() } - + use c3; + sub foo { 'A::foo' } + package B; - use base 'A'; - use c3; - sub foo { 'C::foo => ' . (shift)->next::method() } - + use base 'A'; + use c3; + sub foo { 'B::foo => ' . (shift)->next::method() } + + package C; + use base 'A'; + use c3; + sub foo { 'C::foo => ' . (shift)->next::method() } + package D; - use base ('B', 'C'); - use c3; - sub foo { 'D::foo => ' . (shift)->next::method() } - + use base ('B', 'C'); + use c3; + sub foo { 'D::foo => ' . (shift)->next::method() } + print D->foo; # prints out "D::foo => B::foo => C::foo => A::foo" -A few things to note. First, we do not require you to add on the method name to the C -call (this is unlike C and C which do require that). This helps to enforce the rule -that you cannot dispatch to a method of a different name (this is how C behaves as well). +A few things to note. First, we do not require you to add on the method name to the C +call (this is unlike C and C which do require that). This helps to enforce the rule +that you cannot dispatch to a method of a different name (this is how C behaves as well). -The next thing to keep in mind is that you will need to pass all arguments to C it can -not automatically use the current C<@_>. +The next thing to keep in mind is that you will need to pass all arguments to C. It can +not automatically use the current C<@_>. If C cannot find a next method to re-dispatch the call to, it will throw an exception. You can use C to see if C will succeed before you call it like so: - $self->next::method(@_) if $self->next::can; + $self->next::method(@_) if $self->next::can; -Additionally, you can use C as a shortcut to only call the next method if it exists. +Additionally, you can use C as a shortcut to only call the next method if it exists. The previous example could be simply written as: $self->maybe::next::method(@_); @@ -429,9 +445,9 @@ There are some caveats about using C, see below for those. =head1 CAVEATS -This module used to be labeled as I, however it has now been pretty heavily tested by -the good folks over at L and I am confident this module is perfectly usable for -whatever your needs might be. +This module used to be labeled as I, however it has now been pretty heavily tested by +the good folks over at L and I am confident this module is perfectly usable for +whatever your needs might be. But there are still caveats, so here goes ... @@ -439,46 +455,46 @@ But there are still caveats, so here goes ... =item Use of C. -The idea of C under multiple inheritence is ambigious, and generally not recomended anyway. -However, it's use in conjuntion with this module is very much not recommended, and in fact very +The idea of C under multiple inheritance is ambiguous, and generally not recommended anyway. +However, its use in conjunction with this module is very much not recommended, and in fact very discouraged. The recommended approach is to instead use the supplied C feature, see -more details on it's usage above. +more details on its usage above. =item Changing C<@ISA>. It is the author's opinion that changing C<@ISA> at runtime is pure insanity anyway. However, people do it, so I must caveat. Any changes to the C<@ISA> will not be reflected in the MRO calculated by this -module, and therefor probably won't even show up. If you do this, you will need to call C -in order to recalulate B method dispatch tables. See the C documentation and an example +module, and therefore probably won't even show up. If you do this, you will need to call C +in order to recalculate B method dispatch tables. See the C documentation and an example in F for more information. =item Adding/deleting methods from class symbol tables. -This module calculates the MRO for each requested class by interogatting the symbol tables of said classes. -So any symbol table manipulation which takes place after our INIT phase is run will not be reflected in -the calculated MRO. Just as with changing the C<@ISA>, you will need to call C for any +This module calculates the MRO for each requested class by interrogating the symbol tables of said classes. +So any symbol table manipulation which takes place after our INIT phase is run will not be reflected in +the calculated MRO. Just as with changing the C<@ISA>, you will need to call C for any changes you make to take effect. =item Calling C from methods defined outside the class -There is an edge case when using C from within a subroutine which was created in a different -module than the one it is called from. It sounds complicated, but it really isn't. Here is an example which +There is an edge case when using C from within a subroutine which was created in a different +module than the one it is called from. It sounds complicated, but it really isn't. Here is an example which will not work correctly: *Foo::foo = sub { (shift)->next::method(@_) }; -The problem exists because the anonymous subroutine being assigned to the glob C<*Foo::foo> will show up -in the call stack as being called C<__ANON__> and not C as you might expect. Since C -uses C to find the name of the method it was called in, it will fail in this case. +The problem exists because the anonymous subroutine being assigned to the glob C<*Foo::foo> will show up +in the call stack as being called C<__ANON__> and not C as you might expect. Since C +uses C to find the name of the method it was called in, it will fail in this case. -But fear not, there is a simple solution. The module C will reach into the perl internals and +But fear not, there is a simple solution. The module C will reach into the perl internals and assign a name to an anonymous subroutine for you. Simply do this: - + use Sub::Name 'subname'; *Foo::foo = subname 'Foo::foo' => sub { (shift)->next::method(@_) }; -and things will Just Work. Of course this is not always possible to do, but to be honest, I just can't -manage to find a workaround for it, so until someone gives me a working patch this will be a known +and things will Just Work. Of course this is not always possible to do, but to be honest, I just can't +manage to find a workaround for it, so until someone gives me a working patch this will be a known limitation of this module. =back @@ -491,7 +507,7 @@ If your software is meant to work on earlier Perls, use L as document =head1 Class::C3::XS -This module will load L if it's installed and you are running on a Perl version older than 5.9.5. Installing this is recommended when possible, as it results in significant performance improvements (but unlike the 5.9.5+ core support, it still has all of the same caveats as L). +This module will load L if it's installed and you are running on a Perl version older than 5.9.5. The optional module will be automatically installed for you if a C compiler is available, as it results in significant performance improvements (but unlike the 5.9.5+ core support, it still has all of the same caveats as L). =head1 CODE COVERAGE @@ -503,7 +519,7 @@ L was reporting 94.4% overall test coverage earlier in this module =over 4 -=item L +=item L =back @@ -541,19 +557,19 @@ L was reporting 94.4% overall test coverage earlier in this module =item L -=back +=back =head1 ACKNOWLEGEMENTS =over 4 -=item Thanks to Matt S. Trout for using this module in his module L +=item Thanks to Matt S. Trout for using this module in his module L and finding many bugs and providing fixes. -=item Thanks to Justin Guenther for making C more robust by handling +=item Thanks to Justin Guenther for making C more robust by handling calls inside C and anon-subs. -=item Thanks to Robert Norris for adding support for C and +=item Thanks to Robert Norris for adding support for C and C. =back @@ -571,6 +587,6 @@ Copyright 2005, 2006 by Infinity Interactive, Inc. L This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. +it under the same terms as Perl itself. =cut