next-method
[gitmo/Moose.git] / lib / Moose / Meta / SafeMixin.pm
1
2 package Moose::Meta::SafeMixin;
3
4 use strict;
5 use warnings;
6
7 use Scalar::Util 'blessed';
8 use Carp         'confess';
9
10 our $VERSION = '0.01';
11
12 use base 'Class::MOP::Class';
13
14 sub mixin {
15     # fetch the metaclass for the 
16     # caller and the mixin arg
17     my $metaclass = shift;
18     my $mixin     = $metaclass->initialize(shift);
19     
20     # according to Scala, the 
21     # the superclass of our class
22     # must be a subclass of the 
23     # superclass of the mixin (see above)
24     my ($super_meta)  = $metaclass->superclasses();
25     my ($super_mixin) = $mixin->superclasses();  
26     ($super_meta->isa($super_mixin))
27         || confess "The superclass ($super_meta) must extend a subclass of the " . 
28                    "superclass of the mixin ($super_mixin)"
29                         if defined $super_mixin && defined $super_meta;
30     
31     # check for conflicts here ...
32     
33     $metaclass->has_attribute($_) 
34         && confess "Attribute conflict ($_)"
35             foreach $mixin->get_attribute_list;
36
37     foreach my $method_name ($mixin->get_method_list) {
38         # skip meta, cause everyone has that :)
39         next if $method_name =~ /meta/;
40         $metaclass->has_method($method_name) && confess "Method conflict ($method_name)";
41     }    
42     
43     # collect all the attributes
44     # and clone them so they can 
45     # associate with the new class                  
46     # add all the attributes in ....
47     foreach my $attr ($mixin->get_attribute_list) {
48         $metaclass->add_attribute(
49             $mixin->get_attribute($attr)->clone()
50         );
51     }     
52
53     # add all the methods in ....    
54     foreach my $method_name ($mixin->get_method_list) {
55         # no need to mess with meta
56         next if $method_name eq 'meta';
57         my $method = $mixin->get_method($method_name);
58         # and ignore accessors, the 
59         # attributes take care of that
60         next if blessed($method) && $method->isa('Class::MOP::Attribute::Accessor');
61         $metaclass->alias_method($method_name => $method);
62     }    
63 }
64
65 1;
66
67 __END__
68
69 =pod
70
71 =head1 NAME
72
73 Moose::Meta::SafeMixin - A meta-object for safe mixin-style composition
74
75 =head1 SYNOPSIS
76
77 =head1 DESCRIPTION
78
79 This is a meta-object which provides B<safe> mixin-style composition 
80 of classes. The key word here is "safe" because we enforce a number 
81 of rules about mixing in which prevent some of the instability 
82 inherent in other mixin systems. However, it should be noted that we 
83 still allow you enough rope with which to shoot yourself in the foot 
84 if you so desire.
85
86 =over 4
87
88 =item *
89
90 In order to mix classes together, they must inherit from a common 
91 superclass. This assures at least some level of similarity between 
92 the classes being mixed together, which should result in a more 
93 stable end product.
94
95 The only exception to this rule is if the class being mixed in has 
96 no superclasses at all. In this case we assume the mixin is valid.
97
98 =item * 
99
100 Since we enforce a common ancestral relationship, we need to be 
101 mindful of method and attribute conflicts. The common ancestor 
102 increases the potential of method conflicts because it is common 
103 for subclasses to override their parents methods. However, it is 
104 less common for attributes to be overriden. The way these are  
105 resolved is to use a Trait/Role-style conflict mechanism.
106
107 If two classes are mixed together, any method or attribute conflicts 
108 will result in a failure of the mixin and a fatal exception. It is 
109 not possible to resolve a method or attribute conflict dynamically. 
110 This is because to do so would open the possibility of breaking 
111 classes in very subtle and dangerous ways, particularly in the area 
112 of method interdependencies. The amount of implementation knowledge 
113 which would need to be known by the mixee would (IMO) increase the 
114 complexity of the feature exponentially for each class mixed in.
115
116 However fear not, there is a solution (see below) ...
117
118 =item *
119
120 Safe mixin's offer the possibility of CLOS style I<before>, I<after> 
121 and I<around> methods with which method conflicts can be resolved. 
122
123 A method, which would normally conflict, but which is labeled with 
124 either a I<before>, I<after> or I<around> attribute, will instead be 
125 combined with the original method in the way implied by the attribute.
126
127 The result of this is a generalized event-handling system for classes. 
128 Which can be used to create things more specialized, such as plugins 
129 and decorators.
130
131 =back
132
133 =head2 What kinda crack are you on ?!?!?!?
134
135 This approach may seem crazy, but I am fairly confident that it will 
136 work, and that it will not tie your hands unnessecarily. All these 
137 features have been used with certain degrees of success in the object 
138 systems of other languages, but none (IMO) provided a complete 
139 solution.
140
141 In CLOS, I<before>, I<after> and I<around> methods provide a high 
142 degree of flexibility for adding behavior to methods, but do not address 
143 any concerns regarding classes since in CLOS, classes and methods are 
144 separate components of the system.
145
146 In Scala, mixins are restricted by their ancestral relationships, which 
147 results in a need to have seperate "traits" to get around this restriction. 
148 In addition, Scala does not seem to have any means of method conflict 
149 resolution for mixins (at least not that I can find).
150
151 In Perl 6, the role system forces manual disambiguation which (as 
152 mentioned above) can cause issues with method interdependecies when 
153 composing roles together. This problem will grow exponentially in one 
154 direction with each role composed and in the other direction with the 
155 number of roles that role itself is composed of. The result is that the 
156 complexity of the system becomes unmanagable for all but very simple or
157 very shallow roles. Now, this is not to say that roles are unusable, in 
158 fact, this feature (IMO) promotes good useage of roles by keeping them 
159 both small and simple. But, the same behaviors cannot be applied to 
160 class mixins without hitting these barriers all too quickly.
161
162 The same too can be said of the original Traits system, with its 
163 features for aliasing and exclusion of methods. 
164
165 So after close study of these systems, and in some cases actually 
166 implementing said systems, I have come to the see that each on it's 
167 own is not robust enough and that combining the best parts of each 
168 gives us (what I hope is) a better, safer and saner system.
169
170 =head1 METHODS
171
172 =over 4
173
174 =item B<mixin ($mixin)>
175
176 =back
177
178 =head1 AUTHOR
179
180 Stevan Little E<lt>stevan@iinteractive.comE<gt>
181
182 =head1 COPYRIGHT AND LICENSE
183
184 Copyright 2006 by Infinity Interactive, Inc.
185
186 L<http://www.iinteractive.com>
187
188 This library is free software; you can redistribute it and/or modify
189 it under the same terms as Perl itself. 
190
191 =cut