1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
|
=pod
=encoding utf-8
=head1 NAME
Type::Tiny::Manual::Libraries - defining your own type libraries
=head1 MANUAL
=head2 Defining a Type
A type is an object and you can create a new one using the constructor:
use Type::Tiny;
my $type = Type::Tiny->new(%args);
A full list of the available arguments can be found in the L<Type::Tiny>
documentation, but the most important ones to begin with are:
=over
=item C<name>
The name of your new type. Type::Tiny uses a convention of UpperCamelCase
names for type constraints. The type name may also begin with one or two
leading underscores to indicate a type intended for internal use only.
Types using non-ASCII characters may cause problems on older versions of
Perl (pre-5.8).
Although this is optional and types may be anonymous, a name is required for
a type constraint to added to a type library.
=item C<constraint>
A code reference checking C<< $_ >> and returning a boolean. Alternatively,
a string of Perl code may be provided.
If you've been paying attention, you can probably guess that the string of
Perl code may result in more efficient type checks.
=item C<parent>
An existing type constraint to inherit from. A value will need to pass the
parent constraint before its own constraint would be called.
my $Even = Type::Tiny->new(
name => 'EvenNumber',
parent => Types::Standard::Int,
constraint => sub {
# in this sub we don't need to check that $_ is an Int
# because the parent will take care of that!
$_ % 2 == 0
},
);
Although the C<parent> is optional, it makes sense whenever possible to
inherit from an existing type constraint to benefit from any optimizations
or XS implementations they may provide.
=back
=head2 Defining a Library
A library is a Perl module that exports type constraints as subs.
L<Types::Standard>, L<Types::Common::Numeric>, and L<Types::Common::String>
are type libraries that are bundled with Type::Tiny.
To create a type library, create a package that inherits from
L<Type::Library>.
package MyTypes {
use Type::Library -base;
...; # your type definitions go here
}
The C<< -base >> flag is just a shortcut for:
package MyTypes {
use Type::Library;
our @ISA = 'Type::Library';
}
You can add types like this:
package MyTypes {
use Type::Library -base;
my $Even = Type::Tiny->new(
name => 'EvenNumber',
parent => Types::Standard::Int,
constraint => sub {
# in this sub we don't need to check that $_ is an Int
# because the parent will take care of that!
$_ % 2 == 0
},
);
__PACKAGE__->add_type($Even);
}
There is a shortcut for adding types if they're going to be blessed
L<Type::Tiny> objects and not, for example, a subclass of Type::Tiny.
You can just pass C<< %args >> directly to C<add_type>.
package MyTypes {
use Type::Library -base;
__PACKAGE__->add_type(
name => 'EvenNumber',
parent => Types::Standard::Int,
constraint => sub {
# in this sub we don't need to check that $_ is an Int
# because the parent will take care of that!
$_ % 2 == 0
},
);
}
The C<add_type> method returns the type it just added, so it can be stored in
a variable.
my $Even = __PACKAGE__->add_type(...);
This can be useful if you wish to use C<< $Even >> as the parent type to some
other type you're going to define later.
Here's a bigger worked example:
package Example::Types {
use Type::Library -base;
use Types::Standard -types;
use DateTime;
# Type::Tiny::Class is a subclass of Type::Tiny for creating
# InstanceOf-like types. It's kind of better though because
# it does cool stuff like pass through $type->new(%args) to
# the class's constructor.
#
my $dt = __PACKAGE__->add_type(
Type::Tiny::Class->new(
name => 'Datetime',
class => 'DateTime',
)
);
my $dth = __PACKAGE__->add_type(
name => 'DatetimeHash',
parent => Dict[
year => Int,
month => Optional[ Int ],
day => Optional[ Int ],
hour => Optional[ Int ],
minute => Optional[ Int ],
second => Optional[ Int ],
nanosecond => Optional[ Int ],
time_zone => Optional[ Str ],
],
);
my $eph = __PACKAGE__->add_type(
name => 'EpochHash',
parent => Dict[ epoch => Int ],
);
# Can't just use "plus_coercions" method because that creates
# a new anonymous child type to add the coercions to. We want
# to add them to the type which exists in this library.
#
$dt->coercion->add_type_coercions(
Int, q{ DateTime->from_epoch(epoch => $_) },
Undef, q{ DateTime->now() },
$dth, q{ DateTime->new(%$_) },
$eph, q{ DateTime->from_epoch(%$_) },
);
__PACKAGE__->make_immutable;
}
C<make_immutable> freezes to coercions of all the types in the package,
so no outside code can tamper with the coercions, and allows Type::Tiny
to make optimizations to the coercions, knowing they won't later be
altered. You should always do this at the end.
The library will export types B<Datetime>, B<DatetimeHash>, and
B<EpochHash>. The B<Datetime> type will have coercions from B<Int>,
B<Undef>, B<DatetimeHash>, and B<EpochHash>.
=head2 Extending Libraries
L<Type::Utils> provides a helpful function C<< extends >>.
package My::Types {
use Type::Library -base;
use Type::Utils qw( extends );
BEGIN { extends("Types::Standard") };
# define your own types here
}
The C<extends> function (which you should usually use in a C<< BEGIN { } >>
block not only loads another type library, but it also adds all the types
from it to your library.
This means code using the above My::Types doesn't need to do:
use Types::Standard qw( Str );
use My::Types qw( Something );
It can just do:
use My::Types qw( Str Something );
Because all the types from Types::Standard have been copied across into
My::Types and are also available there.
C<extends> can be passed a list of libraries; you can inherit from multiple
existing libraries. It can also recognize and import types from
L<MooseX::Types>, L<MouseX::Types>, and L<Specio::Exporter> libraries.
Since Type::Library 1.012, there has been a shortcut for C<< extends >>.
package My::Types {
use Type::Library -extends => [ 'Types::Standard' ];
# define your own types here
}
The C<< -extends >> flag takes an arrayref of type libraries to extend.
It automatically implies C<< -base >> so you don't need to use both.
=head2 Custom Error Messages
A type constraint can have custom error messages. It's pretty simple:
Type::Tiny->new(
name => 'EvenNumber',
parent => Types::Standard::Int,
constraint => sub {
# in this sub we don't need to check that $_ is an Int
# because the parent will take care of that!
$_ % 2 == 0
},
message => sub {
sprintf '%s is not an even number', Type::Tiny::_dd($_);
},
);
The message coderef just takes a value in C<< $_ >> and returns a string.
It may use C<< Type::Tiny::_dd() >> as a way of pretty-printing a value.
(Don't be put off by the underscore in the function name. C<< _dd() >>
is an officially supported part of Type::Tiny's API now.)
You don't have to use C<< _dd() >>. You can generate any error string you
like. But C<< _dd() >> will help you make undef and the empty string look
different, and will pretty-print references, and so on.
There's no need to supply an error message coderef unless you really want
custom error messages. The default sub should be reasonable.
=head2 Inlining
In Perl, sub calls are relatively expensive in terms of memory and CPU use.
The B<PositiveInt> type inherits from B<Int> which inherits from B<Num>
which inherits from B<Str> which inherits from B<Defined> which inherits
from B<Item> which inherits from B<Any>.
So you might think that to check of C<< $value >> is a B<PositiveInt>,
it needs to be checked all the way up the inheritance chain. But this is
where one of Type::Tiny's big optimizations happens. Type::Tiny can glue
together a bunch of checks with a stringy eval, and get a single coderef
that can do all the checks in one go.
This is why when Type::Tiny gives you a choice of using a coderef or a
string of Perl code, you should usually choose the string of Perl code.
A single coderef can "break the chain".
But these automatically generated strings of Perl code are not always
as efficient as they could be. For example, imagine that B<HashRef> is
defined as:
my $Defined = Type::Tiny->new(
name => 'Defined',
constraint => 'defined($_)',
);
my $Ref = Type::Tiny->new(
name => 'Ref',
parent => $Defined,
constraint => 'ref($_)',
);
my $HashRef = Type::Tiny->new(
name => 'HashRef',
parent => $Ref,
constraint => 'ref($_) eq "HASH"',
);
Then the combined check is:
defined($_) and ref($_) and ref($_) eq "HASH"
Actually in practice it's even more complicated, because Type::Tiny needs
to localize and set C<< $_ >> first.
But in practice, the following should be a sufficient check:
ref($_) eq "HASH"
It is possible for the B<HashRef> type to have more control over the
string of code generated.
my $HashRef = Type::Tiny->new(
name => 'HashRef',
parent => $Ref,
constraint => 'ref($_) eq "HASH"',
inlined => sub {
my $varname = pop;
sprintf 'ref(%s) eq "HASH"', $varname;
},
);
The inlined coderef gets passed the name of a variable to check. This could
be C<< '$_' >> or C<< '$var' >> or C<< $some{deep}{thing}[0] >>. Because it
is passed the name of a variable to check, instead of always checking
C<< $_ >>, this enables very efficient checking for parameterized types.
Although in this case, the inlining coderef is just returning a string,
technically it returns a list of strings. If there's multiple strings,
Type::Tiny will join them together in a big "&&" statement.
As a special case, if the first item in the returned list of strings is
undef, then Type::Tiny will substitute the parent type constraint's inlined
string in its place. So an inlieing coderef for even numbers might be:
Type::Tiny->new(
name => 'EvenNumber',
parent => Types::Standard::Int,
constraint => sub { $_ % 2 == 0 },
inlined => sub {
my $varname = pop;
return (undef, "$varname % 2 == 0");
},
);
Even if you provide a coderef as a string, an inlining coderef has the
potential to generate more efficient code, so you should consider
providing one.
=head2 Pre-Declaring Types
use Type::Library -base,
-declare => qw( Foo Bar Baz );
This declares types B<Foo>, B<Bar>, and B<Baz> at compile time so they can
safely be used as barewords in your type library.
This also allows recursively defined types to (mostly) work!
use Type::Library -base,
-declare => qw( NumericArrayRef );
use Types::Standard qw( Num ArrayRef );
__PACKAGE__->add_type(
name => NumericArrayRef,
parent => ArrayRef->of( Num | NumericArrayRef ),
);
(Support for recursive type definitions added in Type::Library 1.009_000.)
=head2 Parameterizable Types
This is probably the most "meta" concept that is going to be covered.
Building your own type constraint that can be parameterized like
B<ArrayRef> or B<HasMethods>.
The type constraint we'll build will be B<< MultipleOf[$i] >> which
checks that an integer is a multiple of $i.
__PACKAGE__->add_type(
name => 'MultipleOf',
parent => Int,
# This coderef gets passed the contents of the square brackets.
constraint_generator => sub {
my $i = assert_Int(shift);
# needs to return a coderef to use as a constraint for the
# parameterized type
return sub { $_ % $i == 0 };
},
# optional but recommended
inline_generator => sub {
my $i = shift;
return sub {
my $varname = pop;
return (undef, "$varname % $i == 0");
};
},
# probably the most complex bit
coercion_generator => sub {
my $i = $_[2];
require Type::Coercion;
return Type::Coercion->new(
type_coercion_map => [
Num, qq{ int($i * int(\$_/$i)) }
],
);
},
);
Now we can define an even number like this:
__PACKAGE__->add_type(
name => 'EvenNumber',
parent => __PACKAGE__->get_type('MultipleOf')->of(2),
coercion => 1, # inherit from parent
);
Note that it is possible for a type constraint to have a C<constraint>
I<and> a C<constraint_generator>.
BaseType # uses the constraint
BaseType[] # constraint_generator with no arguments
BaseType[$x] # constraint_generator with an argument
In the B<MultipleOf> example above, B<< MultipleOf[] >> with no number would
throw an error because of C<< assert_Int(shift) >> not finding an integer.
But it is certainly possible for B<< BaseType[] >> to be meaningful and
distinct from C<< BaseType >>.
For example, B<Tuple> is just the same as B<ArrayRef> and accepts any
arrayref as being valid. But B<< Tuple[] >> will only accept arrayrefs
with zero elements in them. (Just like B<< Tuple[Any,Any] >> will only
accept arrayrefs with two elements.)
=head1 NEXT STEPS
After that last example, probably have a little lie down. Once you're
recovered, here's your next step:
=over
=item * L<Type::Tiny::Manual::UsingWithMoose>
How to use Type::Tiny with Moose, including the advantages of Type::Tiny
over built-in type constraints, and Moose-specific features.
=back
=head1 AUTHOR
Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2013-2014, 2017-2023 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=head1 DISCLAIMER OF WARRANTIES
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
=cut
|