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