1MooseX::Extended::ManuaUls:e:rOvCeornvtireiwb(u3t)ed PerMlooDsoecXu:m:eEnxttaetnidoend::Manual::Overview(3)
2
3
4
6 MooseX::Extended::Manual::Overview - Work-in-progress overview for
7 MooseX::Extended
8
10 version 0.35
11
13 For most sane codebases, converting from Moose to MooseX::Extended is
14 as simple as "s/Moose/MooseX::Extended/". We try to be backwards-
15 compatible, but some issues are glaring enough that they can't be
16 fixed. Run your tests, folks.
17
18 From there, convert your various "has" attributes to "field" or "param"
19 (and run your tests), and you can also delete the annoying
20 "__PACKAGE__->meta->make_immutable" at the end of every package (run
21 your tests, folks).
22
23 From there, if you're not using signatures, convert your methods to
24 using signatures. You'll almost definitely get test failures there as
25 many functions handle arguments poorly.
26
27 For roles, just "s/Moose::Role/MooseX::Extended::Roles/" and repeat the
28 process (and run your tests, folks).
29
30 Along the way, you can probably delete your references to
31 namespace::autoclean and friends because we provide that for you.
32
34 You can skip this section if you like.
35
36 MooseX::Extended is built on years of experience hacking on Moose and
37 being the lead designer of the Corinna <https://github.com/Ovid/Cor>
38 project to bring modern OO to the Perl language. We love Moose, but
39 over the years, it's become clear that there are some problematic
40 design choices. Plus, Corinna is not yet in core as We write this
41 (though the Perl Steering Committee has accepted it), so for now, let's
42 see how far we can push the envelope.
43
45 This:
46
47 package My::Class {
48 use MooseX::Extended;
49
50 ... your code here
51 }
52
53 Is sort of the equivalent to:
54
55 package My::Class {
56 use v5.20.0;
57 use Moose;
58 use MooseX::StrictConstructor;
59 use feature 'signatures';
60 no warnings 'experimental::signatures';
61 use namespace::autoclean;
62 use Carp;
63 use mro 'c3';
64
65 ... your code here
66
67 __PACKAGE__->meta->make_immutable;
68 }
69
70 1;
71
72 We get tired of typing a lot of boilerplate, so "MooseX::Extended" does
73 away with it.
74
76 The constructor behavior for Moose could use some love.
77
79 We've regularly face the following problem:
80
81 package Some::Class;
82
83 use Moose;
84
85 has name => (...);
86 has uuid => (...);
87 has id => (...);
88 has backlog => (...);
89 has auth => (...);
90 has username => (...);
91 has password => (...);
92 has cache => (...);
93 has this => (...);
94 has that => (...);
95
96 Which of those should be passed to the constructor and which should
97 not? Just because you can pass something to the constructor doesn't
98 mean you should. Unfortunately, Moose defaults to "opt-out" rather than
99 "opt-in" for constructor arguments. This makes it really easy to build
100 objects, but means that you can pass things to the constructor and it
101 won't always work the way you want it to.
102
103 There's an arcane "init_arg => undef" pair to pass to each to say "this
104 cannot be set via the constructor," but many developers are either
105 unaware of this is simply forget about it. "MooseX::Extended" solves
106 with by separating "has" into "param" (allowed in the constructor, but
107 you can also use "default" or "builder") and "field", which is
108 forbidden in the constructor. We can rewrite the above as this:
109
110 package Some::Class;
111
112 use MooseX::Extended;
113
114 param name => (...);
115 param backlog => (...);
116 param auth => (...);
117 param username => (...);
118 param password => (...);
119
120 field cache => (...);
121 field this => (...);
122 field that => (...);
123 field uuid => (...);
124 field id => (...);
125
126 And now you can instantly see what is and is not intended to be allowed
127 in the constructor.
128
129 Note that in our experience, "field" attributes often depend on "param"
130 attributes, so they're lazy by default (a nice performance win if you
131 don't call them), but you can still pass "lazy => 0" to override this.
132
133 Unknown arguments to the constructor
134 Here's another fun bug:
135
136 my $object = Some::Class->new(
137 name => $name,
138 seriel => $serial,
139 );
140
141 # later in your code
142
143 if ( $object->serial ) {
144 # unreachable code
145 }
146
147 This is because Moose, by default, ignores any unknown arguments to the
148 constructor and in the above example, we misspelled "serial" as
149 "seriel".
150
151 "MooseX::Extended" applies "MooseX::StrictConstructor" to your class so
152 you never have to face this problem again.
153
154 WARNING: Be careful when using this in existing class hierarchies.
155 While "MooseX::Extended" is compatible with Moose (Moose classes can
156 always use "MooseX::Extended" classes and roles), the reverse isn't
157 always true. We've found, for example, that trying to use
158 "MooseX::StrictConstructor" with DBIx::Class does not work.
159
160 Method Resolution Order (mro)
161 If you don't use multiple inheritance, you won't need to worry about
162 this. However, by now, it's generally agreed that the C3 method
163 resolution order (breadth-first, for Perl) is superior to the depth-
164 first default. You can read about the diamond inheritance problem
165 <https://en.wikipedia.org/wiki/Multiple_inheritance> if you'd like to
166 learn more.
167
168 Rather than remembering to include "use mro 'c3'" in your code,
169 MooseX::Extended does it for you.
170
171 If you have existing code that breaks under this, you should
172 investigate carefully. You probably have a bug in your code.
173
174 Making Your Class Immutable
175 It's recommended that you end your Moose classes with this:
176
177 __PACKAGE__->meta->make_immutable;
178
179 That causes a lot of things to happen under the hood. It makes your
180 class much harder to debug as you're in a twisty maze of "eval"ed
181 methods, but it makes your class run much faster. The performance gain
182 is often significant and you almost always want to use this, but it's
183 easy to forget, so we add it for you.
184
185 To do this, we use the "after_runtime" function from
186 "B::Hooks::AtRuntime". However, that doesn't work under the debugger.
187 So if you're running under the debugger, we disable this. Please keep
188 that in mind. As a convenience, when running under the debugger, we
189 issue a series of warnings for every class that is impacted. For a
190 large codebase, that could be a considerable number of classes if they
191 use "MooseX::Extended":
192
193 We are running under the debugger. My::Name is not immutable
194 We are running under the debugger. My::Product is not immutable
195 We are running under the debugger. My::Order is not immutable
196
198 The subject of immutable objects has been done to death. If We set the
199 value of an attribute but another section of the code has already
200 fetched that value, you might have two sections of the code operating
201 under completely different assumptions of what they're allowed to do.
202 So by default, all attributes are "read-only":
203
204 param name => ( isa => NonEmptyStr );
205 field cache => ( isa => HashRef );
206
207 You can change this if you need to:
208
209 param name => ( is => 'rw', isa => NonEmptyStr );
210 field cache => ( is => 'rw', isa => HashRef );
211
212 However, what's going on with that "cache" attribute? It returns a
213 reference. If your code mutates that reference, every bit of code
214 holding a reference to that object silently has its state changed. So
215 we fixed that, too:
216
217 field cache => ( isa => HashRef, clone => 1 );
218
219 Now, every time you get or set that data, it's cloned, ensuring that
220 you can do this:
221
222 # assumes that the original ->cache has a SeKreT key.
223 my $hash1 = $object->cache;
224 delete $hash1->{SeKreT};
225 my $hash2 = $object->cache;
226 my $SeKreT = $hash2->{SeKreT}; # you get the original value
227
228 Internally, we use Storable's "dclone" method for this. Be aware that
229 many things cannot be safely cloned (e.g., database handles).
230
231 The "clone => 1" feature is safest when you don't have objects that
232 you're trying to clone. As a result, this feature is EXPERIMENTAL.
233
235 By now, you know that "is => 'ro'" is the default for all "param" and
236 "field" attributes. You can still use "has", but you will still need to
237 use "is => ..." with that:
238
239 has name => ( is => 'ro, ... );
240
241 However, I hate typing out something like "predicate => 'is_assigned'".
242 Or should it be "predicate => 'has_assigned'"? For a variety of
243 attributes, we've made this simpler.
244
245 When using "field" or "param", we have some attribute shortcuts:
246
247 param name => (
248 isa => NonEmptyStr,
249 writer => 1, # set_name
250 reader => 1, # get_name
251 predicate => 1, # has_name
252 clearer => 1, # clear_name
253 builder => 1, # _build_name
254 );
255
256 sub _build_name ($self) {
257 ...
258 }
259
260 These can also be used when you pass an array reference to the
261 function:
262
263 package Point {
264 use MooseX::Extended types => 'Int';
265
266 param [ 'x', 'y' ] => (
267 isa => Int,
268 clearer => 1, # clear_x and clear_y available
269 default => 0,
270 ) :;
271 }
272
273 Note that these are shortcuts and they make attributes easier to write
274 and more consistent. However, you can still use full names:
275
276 field authz_delegate => (
277 builder => '_build_my_darned_authz_delegate',
278 );
279
280 "writer"
281 If an attribute has "writer" is set to 1 (the number one), a method
282 named "set_$attribute_name" is created.
283
284 This:
285
286 param title => (
287 isa => Undef | NonEmptyStr,
288 default => undef,
289 writer => 1,
290 );
291
292 Is the same as this:
293
294 has title => (
295 is => 'rw', # we change this from 'ro'
296 isa => Undef | NonEmptyStr,
297 default => undef,
298 writer => 'set_title',
299 );
300
301 "reader"
302 By default, the reader (accessor) for the attribute is the same as the
303 name. You can always change this:
304
305 has payload => ( is => 'ro', reader => 'the_payload' );
306
307 However, if you want to change the reader name
308
309 If an attribute has "reader" is set to 1 (the number one), a method
310 named "get_$attribute_name" is created.
311
312 This:
313
314 param title => (
315 isa => Undef | NonEmptyStr,
316 default => undef,
317 reader => 1,
318 );
319
320 Is the same as this:
321
322 has title => (
323 is => 'rw', # we change this from 'ro'
324 isa => Undef | NonEmptyStr,
325 default => undef,
326 reader => 'get_title',
327 );
328
329 "predicate"
330 If an attribute has "predicate" is set to 1 (the number one), a method
331 named "has_$attribute_name" is created.
332
333 This:
334
335 param title => (
336 isa => Undef | NonEmptyStr,
337 default => undef,
338 predicate => 1,
339 );
340
341 Is the same as this:
342
343 has title => (
344 is => 'ro',
345 isa => Undef | NonEmptyStr,
346 default => undef,
347 predicate => 'has_title',
348 );
349
350 "clearer"
351 If an attribute has "clearer" is set to 1 (the number one), a method
352 named "clear_$attribute_name" is created.
353
354 This:
355
356 param title => (
357 isa => Undef | NonEmptyStr,
358 default => undef,
359 clearer => 1,
360 );
361
362 Is the same as this:
363
364 has title => (
365 is => 'ro',
366 isa => Undef | NonEmptyStr,
367 default => undef,
368 clearer => 'clear_title',
369 );
370
371 "builder"
372 If an attribute has "builder" is set to 1 (the number one), a method
373 named "_build_$attribute_name".
374
375 This:
376
377 param title => (
378 isa => NonEmptyStr,
379 builder => 1,
380 );
381
382 Is the same as this:
383
384 has title => (
385 is => 'ro',
386 isa => NonEmptyStr,
387 builder => '_build_title',
388 );
389
390 Obviously, a "private" attribute, such as "_auth_token" would get a
391 build named "_build__auth_token" (note the two underscores between
392 "build" and "auth_token").
393
395 Curtis "Ovid" Poe <curtis.poe@gmail.com>
396
398 This software is Copyright (c) 2022 by Curtis "Ovid" Poe.
399
400 This is free software, licensed under:
401
402 The Artistic License 2.0 (GPL Compatible)
403
404
405
406perl v5.36.1 2023-06-M2o6oseX::Extended::Manual::Overview(3)