Commit | Line | Data |
52d358e2 |
1 | package MooseX::Types; |
16ddefbf |
2 | use Moose; |
8af0a70d |
3 | |
ef8b7b7a |
4 | # ABSTRACT: Organise your Moose types in libraries |
8af0a70d |
5 | |
8af0a70d |
6 | use Moose::Util::TypeConstraints; |
4c2125a4 |
7 | use MooseX::Types::TypeDecorator; |
b0db42a9 |
8 | use MooseX::Types::Base (); |
9 | use MooseX::Types::Util qw( filter_tags ); |
52d358e2 |
10 | use MooseX::Types::UndefinedType; |
b0db42a9 |
11 | use MooseX::Types::CheckedUtilExports (); |
12 | use Carp::Clan qw( ^MooseX::Types ); |
13 | use Sub::Name; |
14 | use Scalar::Util 'reftype'; |
9616cebc |
15 | |
4eb26e95 |
16 | use namespace::autoclean; |
8af0a70d |
17 | |
eba48805 |
18 | use 5.008; |
8af0a70d |
19 | my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'}; |
20 | |
8bf27efd |
21 | =pod |
22 | |
8af0a70d |
23 | =head1 SYNOPSIS |
24 | |
9616cebc |
25 | =head2 Library Definition |
26 | |
8af0a70d |
27 | package MyLibrary; |
8af0a70d |
28 | |
29 | # predeclare our own types |
7b880bf1 |
30 | use MooseX::Types -declare => [ |
31 | qw( |
32 | PositiveInt |
33 | NegativeInt |
34 | ArrayRefOfPositiveInt |
35 | ArrayRefOfAtLeastThreeNegativeInts |
36 | LotsOfInnerConstraints |
37 | StrOrArrayRef |
38 | MyDateTime |
39 | ) |
40 | ]; |
8af0a70d |
41 | |
42 | # import builtin types |
b0db42a9 |
43 | use MooseX::Types::Moose qw/Int HashRef/; |
8af0a70d |
44 | |
77134b88 |
45 | # type definition. |
7b880bf1 |
46 | subtype PositiveInt, |
47 | as Int, |
8af0a70d |
48 | where { $_ > 0 }, |
49 | message { "Int is not larger than 0" }; |
7b880bf1 |
50 | |
8af0a70d |
51 | subtype NegativeInt, |
52 | as Int, |
53 | where { $_ < 0 }, |
54 | message { "Int is not smaller than 0" }; |
55 | |
56 | # type coercion |
57 | coerce PositiveInt, |
58 | from Int, |
59 | via { 1 }; |
60 | |
d9002a85 |
61 | # with parameterized constraints. |
7b880bf1 |
62 | |
475bbd1d |
63 | subtype ArrayRefOfPositiveInt, |
d9002a85 |
64 | as ArrayRef[PositiveInt]; |
7b880bf1 |
65 | |
475bbd1d |
66 | subtype ArrayRefOfAtLeastThreeNegativeInts, |
d9002a85 |
67 | as ArrayRef[NegativeInt], |
475bbd1d |
68 | where { scalar(@$_) > 2 }; |
69 | |
70 | subtype LotsOfInnerConstraints, |
d9002a85 |
71 | as ArrayRef[ArrayRef[HashRef[Int]]]; |
7b880bf1 |
72 | |
475bbd1d |
73 | # with TypeConstraint Unions |
7b880bf1 |
74 | |
475bbd1d |
75 | subtype StrOrArrayRef, |
76 | as Str|ArrayRef; |
77 | |
6cfbfdbc |
78 | # class types |
79 | |
b0db42a9 |
80 | class_type 'DateTime'; |
81 | |
6cfbfdbc |
82 | # or better |
83 | |
84 | class_type MyDateTime, { class => 'DateTime' }; |
85 | |
86 | coerce MyDateTime, |
b0db42a9 |
87 | from HashRef, |
88 | via { DateTime->new(%$_) }; |
89 | |
8af0a70d |
90 | 1; |
91 | |
9616cebc |
92 | =head2 Usage |
93 | |
8af0a70d |
94 | package Foo; |
95 | use Moose; |
96 | use MyLibrary qw( PositiveInt NegativeInt ); |
97 | |
98 | # use the exported constants as type names |
99 | has 'bar', |
100 | isa => PositiveInt, |
101 | is => 'rw'; |
102 | has 'baz', |
103 | isa => NegativeInt, |
104 | is => 'rw'; |
105 | |
106 | sub quux { |
107 | my ($self, $value); |
108 | |
109 | # test the value |
110 | print "positive\n" if is_PositiveInt($value); |
111 | print "negative\n" if is_NegativeInt($value); |
112 | |
113 | # coerce the value, NegativeInt doesn't have a coercion |
114 | # helper, since it didn't define any coercions. |
115 | $value = to_PositiveInt($value) or die "Cannot coerce"; |
116 | } |
117 | |
118 | 1; |
119 | |
120 | =head1 DESCRIPTION |
121 | |
93f28db7 |
122 | The type system provided by Moose effectively makes all of its builtin type |
123 | global, as are any types you declare with Moose. This means that every module |
8bf27efd |
124 | that declares a type named C<PositiveInt> is sharing the same type object. This |
93f28db7 |
125 | can be a problem when different parts of the code base want to use the same |
126 | name for different things. |
8af0a70d |
127 | |
93f28db7 |
128 | This package lets you declare types using short names, but behind the scenes |
129 | it namespaces all your type declarations, effectively prevent name clashes |
130 | between packages. |
8af0a70d |
131 | |
93f28db7 |
132 | This is done by creating a type library module like C<MyApp::Types> and then |
133 | importing types from that module into other modules. |
8af0a70d |
134 | |
93f28db7 |
135 | As a side effect, the declaration mechanism allows you to write type names as |
136 | barewords (really function calls), which catches typos in names at compile |
137 | time rather than run time. |
b0db42a9 |
138 | |
93f28db7 |
139 | This module also provides some helper functions for using Moose types outside |
140 | of attribute declarations. |
8af0a70d |
141 | |
93f28db7 |
142 | If you mix string-based names with types created by this module, it will warn, |
143 | with a few exceptions. If you are declaring a C<class_type()> or |
e5c925c6 |
144 | C<role_type()> within your type library, or if you use a fully qualified name |
93f28db7 |
145 | like C<"MyApp::Foo">. |
8af0a70d |
146 | |
147 | =head1 LIBRARY DEFINITION |
148 | |
e9dc30af |
149 | A MooseX::Types is just a normal Perl module. Unlike Moose |
8af0a70d |
150 | itself, it does not install C<use strict> and C<use warnings> in your |
151 | class by default, so this is up to you. |
152 | |
153 | The only thing a library is required to do is |
154 | |
52d358e2 |
155 | use MooseX::Types -declare => \@types; |
8af0a70d |
156 | |
157 | with C<@types> being a list of types you wish to define in this library. |
158 | This line will install a proper base class in your package as well as the |
e9dc30af |
159 | full set of L<handlers|/"TYPE HANDLER FUNCTIONS"> for your declared |
8af0a70d |
160 | types. It will then hand control over to L<Moose::Util::TypeConstraints>' |
161 | C<import> method to export the functions you will need to declare your |
162 | types. |
163 | |
e9dc30af |
164 | If you want to use Moose' built-in types (e.g. for subtyping) you will |
165 | want to |
8af0a70d |
166 | |
52d358e2 |
167 | use MooseX::Types::Moose @types; |
8af0a70d |
168 | |
52d358e2 |
169 | to import the helpers from the shipped L<MooseX::Types::Moose> |
8af0a70d |
170 | library which can export all types that come with Moose. |
171 | |
172 | You will have to define coercions for your types or your library won't |
173 | export a L</to_$type> coercion helper for it. |
174 | |
e9dc30af |
175 | Note that you currently cannot define types containing C<::>, since |
249888e7 |
176 | exporting would be a problem. |
177 | |
559cf3d8 |
178 | You also don't need to use C<warnings> and C<strict>, since the |
179 | definition of a library automatically exports those. |
180 | |
8af0a70d |
181 | =head1 LIBRARY USAGE |
182 | |
183 | You can import the L<"type helpers"|/"TYPE HANDLER FUNCTIONS"> of a |
184 | library by C<use>ing it with a list of types to import as arguments. If |
185 | you want all of them, use the C<:all> tag. For example: |
186 | |
187 | use MyLibrary ':all'; |
188 | use MyOtherLibrary qw( TypeA TypeB ); |
189 | |
52d358e2 |
190 | MooseX::Types comes with a library of Moose' built-in types called |
191 | L<MooseX::Types::Moose>. |
8af0a70d |
192 | |
16ddefbf |
193 | The exporting mechanism is, since version 0.5, implemented via a wrapper |
194 | around L<Sub::Exporter>. This means you can do something like this: |
195 | |
196 | use MyLibrary TypeA => { -as => 'MyTypeA' }, |
197 | TypeB => { -as => 'MyTypeB' }; |
198 | |
93f28db7 |
199 | =head1 TYPE HANDLER FUNCTIONS |
200 | |
201 | =head2 $type |
202 | |
203 | A constant with the name of your type. It contains the type's fully |
204 | qualified name. Takes no value, as all constants. |
205 | |
206 | =head2 is_$type |
207 | |
208 | This handler takes a value and tests if it is a valid value for this |
209 | C<$type>. It will return true or false. |
210 | |
211 | =head2 to_$type |
212 | |
213 | A handler that will take a value and coerce it into the C<$type>. It will |
214 | return a false value if the type could not be coerced. |
215 | |
216 | B<Important Note>: This handler will only be exported for types that can |
217 | do type coercion. This has the advantage that a coercion to a type that |
218 | has not defined any coercions will lead to a compile-time error. |
219 | |
c20dc98b |
220 | =head1 WRAPPING A LIBRARY |
221 | |
222 | You can define your own wrapper subclasses to manipulate the behaviour |
223 | of a set of library exports. Here is an example: |
224 | |
225 | package MyWrapper; |
226 | use strict; |
df773753 |
227 | use MRO::Compat; |
52d358e2 |
228 | use base 'MooseX::Types::Wrapper'; |
c20dc98b |
229 | |
230 | sub coercion_export_generator { |
231 | my $class = shift; |
232 | my $code = $class->next::method(@_); |
233 | return sub { |
234 | my $value = $code->(@_); |
235 | warn "Coercion returned undef!" |
236 | unless defined $value; |
237 | return $value; |
238 | }; |
239 | } |
240 | |
241 | 1; |
242 | |
243 | This class wraps the coercion generator (e.g., C<to_Int()>) and warns |
244 | if a coercion returned an undefined value. You can wrap any library |
245 | with this: |
246 | |
247 | package Foo; |
248 | use strict; |
249 | use MyWrapper MyLibrary => [qw( Foo Bar )], |
250 | Moose => [qw( Str Int )]; |
251 | |
252 | ... |
253 | 1; |
254 | |
93f28db7 |
255 | The C<Moose> library name is a special shortcut for L<MooseX::Types::Moose>. |
c20dc98b |
256 | |
257 | =head2 Generator methods you can overload |
258 | |
259 | =over 4 |
260 | |
261 | =item type_export_generator( $short, $full ) |
262 | |
93f28db7 |
263 | Creates a closure returning the type's L<Moose::Meta::TypeConstraint> object. |
c20dc98b |
264 | |
265 | =item check_export_generator( $short, $full, $undef_message ) |
266 | |
267 | This creates the closure used to test if a value is valid for this type. |
268 | |
269 | =item coercion_export_generator( $short, $full, $undef_message ) |
270 | |
271 | This is the closure that's doing coercions. |
272 | |
273 | =back |
274 | |
275 | =head2 Provided Parameters |
276 | |
277 | =over 4 |
278 | |
279 | =item $short |
280 | |
281 | The short, exported name of the type. |
282 | |
283 | =item $full |
284 | |
285 | The fully qualified name of this type as L<Moose> knows it. |
286 | |
287 | =item $undef_message |
288 | |
289 | A message that will be thrown when type functionality is used but the |
290 | type does not yet exist. |
291 | |
0ad3779e |
292 | =back |
293 | |
077ac262 |
294 | =head1 RECURSIVE SUBTYPES |
295 | |
296 | As of version 0.08, L<Moose::Types> has experimental support for Recursive |
297 | subtypes. This will allow: |
298 | |
299 | subtype Tree() => as HashRef[Str|Tree]; |
300 | |
301 | Which validates things like: |
302 | |
303 | {key=>'value'}; |
304 | {key=>{subkey1=>'value', subkey2=>'value'}} |
93f28db7 |
305 | |
077ac262 |
306 | And so on. This feature is new and there may be lurking bugs so don't be afraid |
307 | to hunt me down with patches and test cases if you have trouble. |
308 | |
475bbd1d |
309 | =head1 NOTES REGARDING TYPE UNIONS |
310 | |
311 | L<MooseX::Types> uses L<MooseX::Types::TypeDecorator> to do some overloading |
312 | which generally allows you to easily create union types: |
313 | |
314 | subtype StrOrArrayRef, |
93f28db7 |
315 | as Str|ArrayRef; |
475bbd1d |
316 | |
bac4f72c |
317 | As with parameterized constraints, this overloading extends to modules using the |
475bbd1d |
318 | types you define in a type library. |
319 | |
93f28db7 |
320 | use Moose; |
321 | use MooseX::Types::Moose qw(HashRef Int); |
322 | |
323 | has 'attr' => ( isa => HashRef | Int ); |
475bbd1d |
324 | |
325 | And everything should just work as you'd think. |
77134b88 |
326 | |
8af0a70d |
327 | =head1 METHODS |
328 | |
329 | =head2 import |
330 | |
93f28db7 |
331 | Installs the L<MooseX::Types::Base> class into the caller and exports types |
332 | according to the specification described in L</"LIBRARY DEFINITION">. This |
333 | will continue to L<Moose::Util::TypeConstraints>' C<import> method to export |
334 | helper functions you will need to declare your types. |
e211870f |
335 | |
8af0a70d |
336 | =cut |
337 | |
338 | sub import { |
339 | my ($class, %args) = @_; |
5c3e47c7 |
340 | my $caller = caller; |
8af0a70d |
341 | |
559cf3d8 |
342 | # everyone should want this |
343 | strict->import; |
344 | warnings->import; |
345 | |
8af0a70d |
346 | # inject base class into new library |
347 | { no strict 'refs'; |
5c3e47c7 |
348 | unshift @{ $caller . '::ISA' }, 'MooseX::Types::Base'; |
8af0a70d |
349 | } |
350 | |
351 | # generate predeclared type helpers |
e211870f |
352 | if (my @orig_declare = @{ $args{ -declare } || [] }) { |
353 | my ($tags, $declare) = filter_tags @orig_declare; |
16ddefbf |
354 | my @to_export; |
e211870f |
355 | |
356 | for my $type (@$declare) { |
249888e7 |
357 | |
358 | croak "Cannot create a type containing '::' ($type) at the moment" |
359 | if $type =~ /::/; |
360 | |
16ddefbf |
361 | # add type to library and remember to export |
5c3e47c7 |
362 | $caller->add_type($type); |
16ddefbf |
363 | push @to_export, $type; |
8af0a70d |
364 | } |
16ddefbf |
365 | |
5c3e47c7 |
366 | $caller->import({ -full => 1, -into => $caller }, @to_export); |
8af0a70d |
367 | } |
368 | |
369 | # run type constraints import |
5c3e47c7 |
370 | Moose::Util::TypeConstraints->import({ into => $caller }); |
b0db42a9 |
371 | |
372 | # override some with versions that check for syntax errors |
5c3e47c7 |
373 | MooseX::Types::CheckedUtilExports->import({ into => $caller }); |
b0db42a9 |
374 | |
375 | 1; |
8af0a70d |
376 | } |
377 | |
378 | =head2 type_export_generator |
379 | |
e211870f |
380 | Generate a type export, e.g. C<Int()>. This will return either a |
381 | L<Moose::Meta::TypeConstraint> object, or alternatively a |
93f28db7 |
382 | L<MooseX::Types::UndefinedType> object if the type was not yet defined. |
e211870f |
383 | |
8af0a70d |
384 | =cut |
385 | |
386 | sub type_export_generator { |
a706b0f2 |
387 | my ($class, $type, $name) = @_; |
e9dc30af |
388 | |
686e5888 |
389 | ## Return an anonymous subroutine that will generate the proxied type |
390 | ## constraint for you. |
4e6dc81d |
391 | |
1150ce72 |
392 | return subname "__TYPE__::$name" => sub { |
b0db42a9 |
393 | my $type_constraint = $class->create_base_type_constraint($name); |
394 | |
e088dd03 |
395 | if(defined(my $params = shift @_)) { |
686e5888 |
396 | ## We currently only allow a TC to accept a single, ArrayRef |
397 | ## parameter, as in HashRef[Int], where [Int] is what's inside the |
398 | ## ArrayRef passed. |
b0db42a9 |
399 | if(reftype $params eq 'ARRAY') { |
e088dd03 |
400 | $type_constraint = $class->create_arged_type_constraint($name, @$params); |
b0db42a9 |
401 | } elsif(!defined $type_constraint) { |
402 | croak "Syntax error in type definition (did you forget a comma" |
403 | . " after $type?)"; |
e088dd03 |
404 | } else { |
b0db42a9 |
405 | croak "Argument must be an ArrayRef to create a parameterized " |
406 | . "type, Eg.: ${type}[Int]. Got: ".ref($params)."." |
e088dd03 |
407 | } |
e088dd03 |
408 | } |
e7d06577 |
409 | |
e088dd03 |
410 | $type_constraint = defined($type_constraint) ? $type_constraint |
411 | : MooseX::Types::UndefinedType->new($name); |
e9dc30af |
412 | |
d9002a85 |
413 | my $type_decorator = $class->create_type_decorator($type_constraint); |
e9dc30af |
414 | |
686e5888 |
415 | ## If there are additional args, that means it's probably stuff that |
416 | ## needs to be returned to the subtype. Not an ideal solution here but |
417 | ## doesn't seem to cause trouble. |
e9dc30af |
418 | |
d9002a85 |
419 | if(@_) { |
420 | return ($type_decorator, @_); |
421 | } else { |
422 | return $type_decorator; |
423 | } |
e211870f |
424 | }; |
8af0a70d |
425 | } |
426 | |
a706b0f2 |
427 | =head2 create_arged_type_constraint ($name, @args) |
428 | |
8bf27efd |
429 | Given a String $name with @args find the matching type constraint and parameterize |
686e5888 |
430 | it with @args. |
a706b0f2 |
431 | |
432 | =cut |
433 | |
434 | sub create_arged_type_constraint { |
e9dc30af |
435 | my ($class, $name, @args) = @_; |
371efa05 |
436 | my $type_constraint = Moose::Util::TypeConstraints::find_or_create_type_constraint("$name"); |
8a58233c |
437 | my $parameterized = $type_constraint->parameterize(@args); |
438 | # It's obnoxious to have to parameterize before looking for the TC, but the |
439 | # alternative is to hard-code the assumption that the name is |
440 | # "$name[$args[0]]", which would be worse. |
64f42303 |
441 | # This breaks MXMS, unfortunately, which relies on things like Tuple[...] |
442 | # creating new type objects each time. |
443 | # if (my $existing = |
444 | # Moose::Util::TypeConstraints::find_type_constraint($parameterized->name)) { |
445 | # return $existing; |
446 | # } |
447 | # Moose::Util::TypeConstraints::register_type_constraint($parameterized); |
8a58233c |
448 | return $parameterized; |
a706b0f2 |
449 | } |
450 | |
451 | =head2 create_base_type_constraint ($name) |
452 | |
93f28db7 |
453 | Given a String $name, find the matching type constraint. |
a706b0f2 |
454 | |
455 | =cut |
456 | |
457 | sub create_base_type_constraint { |
458 | my ($class, $name) = @_; |
459 | return find_type_constraint($name); |
460 | } |
461 | |
462 | =head2 create_type_decorator ($type_constraint) |
463 | |
464 | Given a $type_constraint, return a lightweight L<MooseX::Types::TypeDecorator> |
465 | instance. |
466 | |
467 | =cut |
468 | |
469 | sub create_type_decorator { |
470 | my ($class, $type_constraint) = @_; |
475bbd1d |
471 | return MooseX::Types::TypeDecorator->new($type_constraint); |
a706b0f2 |
472 | } |
473 | |
8af0a70d |
474 | =head2 coercion_export_generator |
475 | |
93f28db7 |
476 | This generates a coercion handler function, e.g. C<to_Int($value)>. |
e211870f |
477 | |
8af0a70d |
478 | =cut |
479 | |
480 | sub coercion_export_generator { |
481 | my ($class, $type, $full, $undef_msg) = @_; |
482 | return sub { |
483 | my ($value) = @_; |
484 | |
485 | # we need a type object |
486 | my $tobj = find_type_constraint($full) or croak $undef_msg; |
487 | my $return = $tobj->coerce($value); |
488 | |
489 | # non-successful coercion returns false |
490 | return unless $tobj->check($return); |
491 | |
492 | return $return; |
493 | } |
494 | } |
495 | |
496 | =head2 check_export_generator |
497 | |
e211870f |
498 | Generates a constraint check closure, e.g. C<is_Int($value)>. |
499 | |
8af0a70d |
500 | =cut |
501 | |
502 | sub check_export_generator { |
503 | my ($class, $type, $full, $undef_msg) = @_; |
504 | return sub { |
505 | my ($value) = @_; |
506 | |
507 | # we need a type object |
508 | my $tobj = find_type_constraint($full) or croak $undef_msg; |
509 | |
510 | return $tobj->check($value); |
511 | } |
512 | } |
513 | |
e211870f |
514 | =head1 CAVEATS |
515 | |
93f28db7 |
516 | The following are lists of gotchas and their workarounds for developers coming |
686e5888 |
517 | from the standard string based type constraint names |
518 | |
519 | =head2 Uniqueness |
520 | |
e211870f |
521 | A library makes the types quasi-unique by prefixing their names with (by |
522 | default) the library package name. If you're only using the type handler |
52d358e2 |
523 | functions provided by MooseX::Types, you shouldn't ever have to use |
e211870f |
524 | a type's actual full name. |
525 | |
686e5888 |
526 | =head2 Argument separation ('=>' versus ',') |
527 | |
93f28db7 |
528 | The L<perlop> manpage has this to say about the '=>' operator: "The => operator is |
686e5888 |
529 | a synonym for the comma, but forces any word (consisting entirely of word |
530 | characters) to its left to be interpreted as a string (as of 5.001). This |
531 | includes words that might otherwise be considered a constant or function call." |
532 | |
533 | Due to this stringification, the following will NOT work as you might think: |
534 | |
93f28db7 |
535 | subtype StrOrArrayRef => as Str | ArrayRef; |
536 | |
8bf27efd |
537 | The C<StrOrArrayRef> type will have its stringification activated -- this causes the |
686e5888 |
538 | subtype to not be created. Since the bareword type constraints are not strings |
539 | you really should not try to treat them that way. You will have to use the ',' |
16b11399 |
540 | operator instead. The authors of this package realize that all the L<Moose> |
c807eb83 |
541 | documentation and examples nearly uniformly use the '=>' version of the comma |
686e5888 |
542 | operator and this could be an issue if you are converting code. |
543 | |
544 | Patches welcome for discussion. |
077ac262 |
545 | |
546 | =head2 Compatibility with Sub::Exporter |
547 | |
548 | If you want to use L<Sub::Exporter> with a Type Library, you need to make sure |
549 | you export all the type constraints declared AS WELL AS any additional export |
550 | targets. For example if you do: |
551 | |
93f28db7 |
552 | package TypeAndSubExporter; |
553 | |
554 | use MooseX::Types::Moose qw(Str); |
555 | use MooseX::Types -declare => [qw(MyStr)]; |
556 | use Sub::Exporter -setup => { exports => [qw(something)] }; |
557 | |
558 | subtype MyStr, as Str; |
559 | |
560 | sub something { |
561 | return 1; |
562 | } |
563 | |
564 | # then in another module ... |
565 | |
566 | package Foo; |
567 | use TypeAndSubExporter qw(MyStr); |
077ac262 |
568 | |
8bf27efd |
569 | You'll get a C<< "MyStr" is not exported by the TypeAndSubExporter module >> error. |
47464996 |
570 | It can be worked around by: |
077ac262 |
571 | |
93f28db7 |
572 | - use Sub::Exporter -setup => { exports => [ qw(something) ] }; |
573 | + use Sub::Exporter -setup => { exports => [ qw(something MyStr) ] }; |
077ac262 |
574 | |
575 | This is a workaround and I am exploring how to make these modules work better |
576 | together. I realize this workaround will lead a lot of duplication in your |
577 | export declarations and will be onerous for large type libraries. Patches and |
578 | detailed test cases welcome. See the tests directory for a start on this. |
29dcd6ad |
579 | |
580 | =head1 COMBINING TYPE LIBRARIES |
581 | |
582 | You may want to combine a set of types for your application with other type |
583 | libraries, like L<MooseX::Types::Moose> or L<MooseX::Types::Common::String>. |
584 | |
585 | The L<MooseX::Types::Combine> module provides a simple API for combining a set |
586 | of type libraries together. |
587 | |
8af0a70d |
588 | =head1 SEE ALSO |
589 | |
93f28db7 |
590 | L<Moose>, L<Moose::Util::TypeConstraints>, L<MooseX::Types::Moose>, |
16ddefbf |
591 | L<Sub::Exporter> |
8af0a70d |
592 | |
b55332a8 |
593 | =head1 ACKNOWLEDGEMENTS |
8af0a70d |
594 | |
b55332a8 |
595 | Many thanks to the C<#moose> cabal on C<irc.perl.org>. |
8af0a70d |
596 | |
8af0a70d |
597 | =cut |