no-more-blessed-subs
[gitmo/Class-MOP.git] / lib / Class / MOP.pm
index 303137f..0ef8e87 100644 (file)
@@ -4,33 +4,53 @@ package Class::MOP;
 use strict;
 use warnings;
 
-use Scalar::Util 'blessed';
 use Carp         'confess';
+use Scalar::Util 'weaken';
 
 use Class::MOP::Class;
 use Class::MOP::Attribute;
 use Class::MOP::Method;
 
-our $VERSION = '0.04';
-
-sub import {
-    shift;
-    return unless @_;
-    if ($_[0] eq ':universal') {
-        *UNIVERSAL::meta = sub { 
-            Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) 
-        };
-    }
-    else {
-        my $pkg = caller();
-        no strict 'refs';
-        *{$pkg . '::' . $_[0]} = sub { 
-            Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) 
-        };        
-    }
+use Class::MOP::Class::Immutable;
+
+our $VERSION   = '0.34';
+our $AUTHORITY = 'cpan:STEVAN';
+
+{
+    # Metaclasses are singletons, so we cache them here.
+    # there is no need to worry about destruction though
+    # because they should die only when the program dies.
+    # After all, do package definitions even get reaped?
+    my %METAS;  
+    
+    # means of accessing all the metaclasses that have 
+    # been initialized thus far (for mugwumps obj browser)
+    sub get_all_metaclasses         {        %METAS         }            
+    sub get_all_metaclass_instances { values %METAS         } 
+    sub get_all_metaclass_names     { keys   %METAS         }     
+    sub get_metaclass_by_name       { $METAS{$_[0]}         }
+    sub store_metaclass_by_name     { $METAS{$_[0]} = $_[1] }  
+    sub weaken_metaclass            { weaken($METAS{$_[0]}) }            
+    sub does_metaclass_exist        { exists $METAS{$_[0]} && defined $METAS{$_[0]} }
+    sub remove_metaclass_by_name    { $METAS{$_[0]} = undef }     
+    
+    # NOTE:
+    # We only cache metaclasses, meaning instances of 
+    # Class::MOP::Class. We do not cache instance of 
+    # Class::MOP::Package or Class::MOP::Module. Mostly
+    # because I don't yet see a good reason to do so.        
 }
 
 ## ----------------------------------------------------------------------------
+## Setting up our environment ...
+## ----------------------------------------------------------------------------
+## Class::MOP needs to have a few things in the global perl environment so 
+## that it can operate effectively. Those things are done here.
+## ----------------------------------------------------------------------------
+
+# ... nothing yet actually ;)
+
+## ----------------------------------------------------------------------------
 ## Bootstrapping 
 ## ----------------------------------------------------------------------------
 ## The code below here is to bootstrap our MOP with itself. This is also 
@@ -45,30 +65,255 @@ sub import {
 # any subclass of Class::MOP::* will be able to 
 # inherit them using &construct_instance
 
+## --------------------------------------------------------
+## Class::MOP::Package
+
+Class::MOP::Package->meta->add_attribute(
+    Class::MOP::Attribute->new('$:package' => (
+        reader   => {
+            # NOTE: we need to do this in order 
+            # for the instance meta-object to 
+            # not fall into meta-circular death
+            'name' => sub { (shift)->{'$:package'} }
+        },
+        init_arg => ':package',
+    ))
+);
+
+Class::MOP::Package->meta->add_attribute(
+    Class::MOP::Attribute->new('%:namespace' => (
+        reader => {
+            # NOTE:
+            # because of issues with the Perl API 
+            # to the typeglob in some versions, we 
+            # need to just always grab a new 
+            # reference to the hash here. Ideally 
+            # we could just store a ref and it would
+            # Just Work, but oh well :\
+            'namespace' => sub { 
+                no strict 'refs';
+                \%{$_[0]->name . '::'} 
+            }
+        },
+        # NOTE:
+        # protect this from silliness 
+        init_arg => '!............( DO NOT DO THIS )............!',
+        default  => sub { \undef }
+    ))
+);
+
+# NOTE:
+# use the metaclass to construct the meta-package
+# which is a superclass of the metaclass itself :P
+Class::MOP::Package->meta->add_method('initialize' => sub {
+    my $class        = shift;
+    my $package_name = shift;
+    $class->meta->new_object(':package' => $package_name, @_);  
+});
+
+## --------------------------------------------------------
+## Class::MOP::Module
+
+# NOTE:
+# yeah this is kind of stretching things a bit, 
+# but truthfully the version should be an attribute
+# of the Module, the weirdness comes from having to 
+# stick to Perl 5 convention and store it in the 
+# $VERSION package variable. Basically if you just 
+# squint at it, it will look how you want it to look. 
+# Either as a package variable, or as a attribute of
+# the metaclass, isn't abstraction great :)
+
+Class::MOP::Module->meta->add_attribute(
+    Class::MOP::Attribute->new('$:version' => (
+        reader => {
+            'version' => sub {  
+                my $self = shift;
+                ${$self->get_package_symbol('$VERSION')};
+            }
+        },
+        # NOTE:
+        # protect this from silliness 
+        init_arg => '!............( DO NOT DO THIS )............!',
+        default  => sub { \undef }
+    ))
+);
+
+# NOTE:
+# By following the same conventions as version here, 
+# we are opening up the possibility that people can 
+# use the $AUTHORITY in non-Class::MOP modules as 
+# well.  
+
+Class::MOP::Module->meta->add_attribute(
+    Class::MOP::Attribute->new('$:authority' => (
+        reader => {
+            'authority' => sub {  
+                my $self = shift;
+                ${$self->get_package_symbol('$AUTHORITY')};
+            }
+        },       
+        # NOTE:
+        # protect this from silliness 
+        init_arg => '!............( DO NOT DO THIS )............!',
+        default  => sub { \undef }
+    ))
+);
+
+## --------------------------------------------------------
 ## Class::MOP::Class
 
 Class::MOP::Class->meta->add_attribute(
-    Class::MOP::Attribute->new('$:pkg' => (
-        init_arg => ':pkg'
+    Class::MOP::Attribute->new('%:attributes' => (
+        reader   => {
+            # NOTE: we need to do this in order 
+            # for the instance meta-object to 
+            # not fall into meta-circular death            
+            'get_attribute_map' => sub { (shift)->{'%:attributes'} }
+        },
+        init_arg => ':attributes',
+        default  => sub { {} }
     ))
 );
 
 Class::MOP::Class->meta->add_attribute(
-    Class::MOP::Attribute->new('%:attrs' => (
-        init_arg => ':attrs',
-        default  => sub { {} }
+    Class::MOP::Attribute->new('%:methods' => (
+        #reader => 'get_method_map',
+        #reader   => {          
+        #    # NOTE:
+        #    # as with the $VERSION and $AUTHORITY above
+        #    # sometimes we don't/can't store directly 
+        #    # inside the instance, so we need the accessor
+        #    # to just DWIM
+        #    'get_method_map' => sub {
+        #        my $self = shift;
+        #        # FIXME:
+        #        # there is a faster/better way 
+        #        # to do this, I am sure :)    
+        #        return +{ 
+        #            map {
+        #                $_ => $self->method_metaclass->wrap($self->get_package_symbol('&' . $_)) 
+        #            } grep { 
+        #                $self->has_package_symbol('&' . $_) 
+        #            } $self->list_all_package_symbols
+        #        };            
+        #    }
+        #},
+        #init_arg => '!............( DO NOT DO THIS )............!',
+        #default  => sub { \undef }
+        default => sub { {} }
+    ))
+);
+
+Class::MOP::Class->meta->add_attribute(
+    Class::MOP::Attribute->new('$:attribute_metaclass' => (
+        reader   => 'attribute_metaclass',
+        init_arg => ':attribute_metaclass',
+        default  => 'Class::MOP::Attribute',
+    ))
+);
+
+Class::MOP::Class->meta->add_attribute(
+    Class::MOP::Attribute->new('$:method_metaclass' => (
+        reader   => 'method_metaclass',
+        init_arg => ':method_metaclass',
+        default  => 'Class::MOP::Method',        
+    ))
+);
+
+Class::MOP::Class->meta->add_attribute(
+    Class::MOP::Attribute->new('$:instance_metaclass' => (
+        reader   => {
+            # NOTE: we need to do this in order 
+            # for the instance meta-object to 
+            # not fall into meta-circular death            
+            'instance_metaclass' => sub { (shift)->{'$:instance_metaclass'} }
+        },
+        init_arg => ':instance_metaclass',
+        default  => 'Class::MOP::Instance',        
     ))
 );
 
+# NOTE:
+# we don't actually need to tie the knot with 
+# Class::MOP::Class here, it is actually handled 
+# within Class::MOP::Class itself in the 
+# construct_class_instance method. 
+
+## --------------------------------------------------------
 ## Class::MOP::Attribute
 
-Class::MOP::Attribute->meta->add_attribute(Class::MOP::Attribute->new('name'));
-Class::MOP::Attribute->meta->add_attribute(Class::MOP::Attribute->new('accessor'));
-Class::MOP::Attribute->meta->add_attribute(Class::MOP::Attribute->new('reader'));
-Class::MOP::Attribute->meta->add_attribute(Class::MOP::Attribute->new('writer'));
-Class::MOP::Attribute->meta->add_attribute(Class::MOP::Attribute->new('predicate'));
-Class::MOP::Attribute->meta->add_attribute(Class::MOP::Attribute->new('init_arg'));
-Class::MOP::Attribute->meta->add_attribute(Class::MOP::Attribute->new('default'));
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('name' => (
+        reader => {
+            # NOTE: we need to do this in order 
+            # for the instance meta-object to 
+            # not fall into meta-circular death            
+            'name' => sub { (shift)->{name} }
+        }
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('associated_class' => (
+        reader => {
+            # NOTE: we need to do this in order 
+            # for the instance meta-object to 
+            # not fall into meta-circular death            
+            'associated_class' => sub { (shift)->{associated_class} }
+        }
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('accessor' => (
+        reader    => 'accessor',
+        predicate => 'has_accessor',
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('reader' => (
+        reader    => 'reader',
+        predicate => 'has_reader',
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('writer' => (
+        reader    => 'writer',
+        predicate => 'has_writer',
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('predicate' => (
+        reader    => 'predicate',
+        predicate => 'has_predicate',
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('clearer' => (
+        reader    => 'clearer',
+        predicate => 'has_clearer',
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('init_arg' => (
+        reader    => 'init_arg',
+        predicate => 'has_init_arg',
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('default' => (
+        # default has a custom 'reader' method ...
+        predicate => 'has_default',
+    ))
+);
+
 
 # NOTE: (meta-circularity)
 # This should be one of the last things done
@@ -82,13 +327,34 @@ Class::MOP::Attribute->meta->add_method('new' => sub {
         
     (defined $name && $name)
         || confess "You must provide a name for the attribute";
-    (!exists $options{reader} && !exists $options{writer})
-        || confess "You cannot declare an accessor and reader and/or writer functions"
-            if exists $options{accessor};
-            
-    bless $class->meta->construct_instance(name => $name, %options) => $class;
+    $options{init_arg} = $name 
+        if not exists $options{init_arg};
+        
+    (Class::MOP::Attribute::is_default_a_coderef(\%options))
+        || confess("References are not allowed as default values, you must ". 
+                   "wrap then in a CODE reference (ex: sub { [] } and not [])")
+            if exists $options{default} && ref $options{default};        
+
+    # return the new object
+    $class->meta->new_object(name => $name, %options);
+});
+
+Class::MOP::Attribute->meta->add_method('clone' => sub {
+    my $self  = shift;
+    $self->meta->clone_object($self, @_);  
 });
 
+## --------------------------------------------------------
+## Now close all the Class::MOP::* classes
+
+Class::MOP::Package  ->meta->make_immutable(inline_constructor => 0);
+Class::MOP::Module   ->meta->make_immutable(inline_constructor => 0);
+Class::MOP::Class    ->meta->make_immutable(inline_constructor => 0);
+Class::MOP::Attribute->meta->make_immutable(inline_constructor => 0);
+Class::MOP::Method   ->meta->make_immutable(inline_constructor => 0);
+Class::MOP::Instance ->meta->make_immutable(inline_constructor => 0);
+Class::MOP::Object   ->meta->make_immutable(inline_constructor => 0);
+
 1;
 
 __END__
@@ -116,6 +382,12 @@ set of extensions to the Perl 5 object system. Every attempt has been
 made for these tools to keep to the spirit of the Perl 5 object 
 system that we all know and love.
 
+This documentation is admittedly sparse on details, as time permits 
+I will try to improve them. For now, I suggest looking at the items 
+listed in the L<SEE ALSO> section for more information. In particular 
+the book "The Art of the Meta Object Protocol" was very influential 
+in the development of this system.
+
 =head2 What is a Meta Object Protocol?
 
 A meta object protocol is an API to an object system. 
@@ -183,6 +455,49 @@ B<any> drain at all upon your code's performance. In fact, by itself
 it does nothing to affect your existing code. So you only pay for 
 what you actually use.
 
+=head2 About Metaclass compatibility
+
+This module makes sure that all metaclasses created are both upwards 
+and downwards compatible. The topic of metaclass compatibility is 
+highly esoteric and is something only encountered when doing deep and 
+involved metaclass hacking. There are two basic kinds of metaclass 
+incompatibility; upwards and downwards. 
+
+Upwards metaclass compatibility means that the metaclass of a 
+given class is either the same as (or a subclass of) all of the 
+class's ancestors.
+
+Downward metaclass compatibility means that the metaclasses of a 
+given class's anscestors are all either the same as (or a subclass 
+of) that metaclass.
+
+Here is a diagram showing a set of two classes (C<A> and C<B>) and 
+two metaclasses (C<Meta::A> and C<Meta::B>) which have correct  
+metaclass compatibility both upwards and downwards.
+
+    +---------+     +---------+
+    | Meta::A |<----| Meta::B |      <....... (instance of  )
+    +---------+     +---------+      <------- (inherits from)  
+         ^               ^
+         :               :
+    +---------+     +---------+
+    |    A    |<----|    B    |
+    +---------+     +---------+
+
+As I said this is a highly esoteric topic and one you will only run 
+into if you do a lot of subclassing of B<Class::MOP::Class>. If you 
+are interested in why this is an issue see the paper 
+I<Uniform and safe metaclass composition> linked to in the 
+L<SEE ALSO> section of this document.
+
+=head2 Using custom metaclasses
+
+Always use the metaclass pragma when using a custom metaclass, this 
+will ensure the proper initialization order and not accidentely 
+create an incorrect type of metaclass for you. This is a very rare 
+problem, and one which can only occur if you are doing deep metaclass 
+programming. So in other words, don't worry about it.
+
 =head1 PROTOCOLS
 
 The protocol is divided into 3 main sub-protocols:
@@ -218,6 +533,42 @@ See L<Class::MOP::Method> for more details.
 
 =back
 
+=head1 FUNCTIONS
+
+Class::MOP holds a cache of metaclasses, the following are functions 
+(B<not methods>) which can be used to access that cache. It is not 
+recommended that you mess with this, bad things could happen. But if 
+you are brave and willing to risk it, go for it.
+
+=over 4
+
+=item B<get_all_metaclasses>
+
+This will return an hash of all the metaclass instances that have 
+been cached by B<Class::MOP::Class> keyed by the package name. 
+
+=item B<get_all_metaclass_instances>
+
+This will return an array of all the metaclass instances that have 
+been cached by B<Class::MOP::Class>.
+
+=item B<get_all_metaclass_names>
+
+This will return an array of all the metaclass names that have 
+been cached by B<Class::MOP::Class>.
+
+=item B<get_metaclass_by_name ($name)>
+
+=item B<store_metaclass_by_name ($name, $meta)>
+
+=item B<weaken_metaclass ($name)>
+
+=item B<does_metaclass_exist ($name)>
+
+=item B<remove_metaclass_by_name ($name)>
+
+=back
+
 =head1 SEE ALSO
 
 =head2 Books
@@ -239,6 +590,29 @@ email me and let me know, I would love to hear about them.
 
 =back
 
+=head2 Papers
+
+=over 4
+
+=item Uniform and safe metaclass composition
+
+An excellent paper by the people who brought us the original Traits paper. 
+This paper is on how Traits can be used to do safe metaclass composition, 
+and offers an excellent introduction section which delves into the topic of 
+metaclass compatibility.
+
+L<http://www.iam.unibe.ch/~scg/Archive/Papers/Duca05ySafeMetaclassTrait.pdf>
+
+=item Safe Metaclass Programming
+
+This paper seems to precede the above paper, and propose a mix-in based 
+approach as opposed to the Traits based approach. Both papers have similar 
+information on the metaclass compatibility problem space. 
+
+L<http://citeseer.ist.psu.edu/37617.html>
+
+=back
+
 =head2 Prior Art
 
 =over 4
@@ -261,30 +635,8 @@ As I have said above, this module is a class-builder-builder, so it is
 not the same thing as modules like L<Class::Accessor> and 
 L<Class::MethodMaker>. That being said there are very few modules on CPAN 
 with similar goals to this module. The one I have found which is most 
-like this module is L<Class::Meta>, although it's philosophy is very 
-different from this module. 
-
-To start with, it provides wrappers around common Perl data types, and even 
-extends those types with more specific subtypes. This module does not 
-go into that area at all. 
-
-L<Class::Meta> also seems to create it's own custom meta-object protocol, 
-which is both more restrictive and more featureful than the vanilla 
-Perl 5 one. This module attempts to model the existing Perl 5 MOP as it is.
-
-It's introspection capabilities also seem to be heavily rooted in this 
-custom MOP, so that you can only introspect classes which are already 
-created with L<Class::Meta>. This module does not make such restictions.
-
-Now, all this said, L<Class::Meta> is much more featureful than B<Class::MOP> 
-would ever try to be. But B<Class::MOP> has some features which L<Class::Meta>
-could not easily implement. It would be very possible to completely re-implement 
-L<Class::Meta> using B<Class::MOP> and bring some of these features to 
-L<Class::Meta> though. 
-
-But in the end, this module's admitedly ambitious goals have no direct equal 
-on CPAN since surely no one has been crazy enough to try something as silly 
-as this ;) until now.
+like this module is L<Class::Meta>, although it's philosophy and the MOP it 
+creates are very different from this modules. 
 
 =head1 BUGS
 
@@ -292,20 +644,44 @@ All complex software has bugs lurking in it, and this module is no
 exception. If you find a bug please either email me, or add the bug
 to cpan-RT.
 
+=head1 CODE COVERAGE
+
+I use L<Devel::Cover> to test the code coverage of my tests, below is the 
+L<Devel::Cover> report on this module's test suite.
+
+ ---------------------------- ------ ------ ------ ------ ------ ------ ------
+ File                           stmt   bran   cond    sub    pod   time  total
+ ---------------------------- ------ ------ ------ ------ ------ ------ ------
+ Class/MOP.pm                   78.0   87.5   55.6   71.4  100.0   12.4   76.8
+ Class/MOP/Attribute.pm         83.4   75.6   86.7   94.4  100.0    8.9   85.2
+ Class/MOP/Class.pm             96.9   75.8   43.2   98.0  100.0   55.3   83.6
+ Class/MOP/Class/Immutable.pm   88.5   53.8    n/a   95.8  100.0    1.1   84.7
+ Class/MOP/Instance.pm          87.9   75.0   33.3   89.7  100.0   10.1   89.1
+ Class/MOP/Method.pm            97.6   60.0   57.9   76.9  100.0    1.5   82.8
+ Class/MOP/Module.pm            87.5    n/a   11.1   83.3  100.0    0.3   66.7
+ Class/MOP/Object.pm           100.0    n/a   33.3  100.0  100.0    0.1   89.5
+ Class/MOP/Package.pm           95.1   69.0   33.3  100.0  100.0    9.9   85.5
+ metaclass.pm                  100.0  100.0   83.3  100.0    n/a    0.5   97.7
+ ---------------------------- ------ ------ ------ ------ ------ ------ ------
+ Total                          91.5   72.1   48.8   90.7  100.0  100.0   84.2
+ ---------------------------- ------ ------ ------ ------ ------ ------ ------
+
 =head1 ACKNOWLEDGEMENTS
 
 =over 4
 
-=item Rob Kinyon E<lt>rob@iinteractive.comE<gt>
+=item Rob Kinyon
 
 Thanks to Rob for actually getting the development of this module kick-started. 
 
 =back
 
-=head1 AUTHOR
+=head1 AUTHORS
 
 Stevan Little E<lt>stevan@iinteractive.comE<gt>
 
+Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
+
 =head1 COPYRIGHT AND LICENSE
 
 Copyright 2006 by Infinity Interactive, Inc.