Commit | Line | Data |
505c6fac |
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)) |
fc5609d2 |
27 | || confess "The superclass ($super_meta) must extend a subclass of the " . |
28 | "superclass of the mixin ($super_mixin)" |
505c6fac |
29 | if defined $super_mixin && defined $super_meta; |
30 | |
fc5609d2 |
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 | |
505c6fac |
43 | # collect all the attributes |
44 | # and clone them so they can |
fc5609d2 |
45 | # associate with the new class |
505c6fac |
46 | # add all the attributes in .... |
fc5609d2 |
47 | foreach my $attr ($mixin->get_attribute_list) { |
48 | $metaclass->add_attribute( |
49 | $mixin->get_attribute($attr)->clone() |
50 | ); |
51 | } |
505c6fac |
52 | |
53 | # add all the methods in .... |
fc5609d2 |
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); |
505c6fac |
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 |