Type::T...WithMoo2(3)
π Type::Tiny::Manual::UsingWithMoo2 π
okay bestie, welcome to Type::Tiny::Manual::UsingWithMoo2 β the advanced use of Type::Tiny with Moo manual, part 1. grab a snack, we're going deep π¦
β¨ NAME β¨
Type::Tiny::Manual::UsingWithMoo2 β advanced use of Type::Tiny with Moo π
π MANUAL π
π§ What is a Type? π§
okay so like, every example so far has been you using types, right? but we never actually cracked one open and looked at what a type is. so let's.
use Types::Standard qw( Int );
my $type = Int;
Int up there is literally just a function called with zero arguments, and what it hands you back is a blessed Perl object. that object is the whole personality of the Int type β it's what defines it and it's the thing responsible for checking that values actually meet the definition. no cap.
use Types::Standard qw( HashRef Int );
my $type = HashRef[Int];
the HashRef function, when you call it with no parameters, returns the object that defines the HashRef type, exactly like Int did before. but here's the glow-up: it's being called with a parameter β an arrayref containing the Int type object. it uses that to build the HashRef[Int] type, and returns it. slay.
and like any object, you can call methods on it. the ones you actually need to know:
# check the value and return a boolean
#
$type->check($value);
# return an error message about $value failing the type check
# but don't actually check the value
#
$type->get_message($value);
# coerce the value
#
my $coerced = $type->coerce($value);
we've already met a few other methods earlier in the tutorial, too:
# create a new type, same as the old type, but that has coercions
#
my $new_type = $type->plus_coercions( ... );
# different syntax for parameterized types
#
my $href = HashRef;
my $int = Int;
my $href_of_int = $href->of($int);
so now this whole thing should click for you:
use Types::Standard qw( ArrayRef Dict Optional );
use Types::Common::Numeric qw( PositiveInt );
use Types::Common::String qw( NonEmptyStr );
my $RaceInfo = Dict[
year => PositiveInt,
race => NonEmptyStr,
jockey => Optional[NonEmptyStr],
];
has latest_event => ( is => 'rw', isa => $RaceInfo );
has wins => ( is => 'rw', isa => ArrayRef[$RaceInfo] );
has losses => ( is => 'rw', isa => ArrayRef[$RaceInfo] );
this is how you dodge repeating yourself when you've got a big fancy parameterized type that you need to reuse a few times. it's giving DRY, it's giving main character π
π "where" π
one of the most useful methods you can call on a type object is where.
use Types::Standard qw( Int );
has lucky_number => (
is => 'ro',
isa => Int->where(sub { $_ != 13 }),
);
I think you already get what it does tbh β it creates a brand new type constraint on the fly, restricting the original type. obsessed.
and like with coercions, these restrictions can be written as a coderef or as a string of Perl code that operates on the $_ variable. and also like with coercions: using a string of code gets you better performance. β¨
use Types::Standard qw( Int );
has lucky_number => (
is => 'ro',
isa => Int->where(q{ $_ != 13 }),
);
ok now let's coerce a hashref of strings out of an even-sized arrayref of strings:
use Types::Standard qw( HashRef ArrayRef Str );
has stringhash => (
is => 'ro',
isa => HashRef->of(Str)->plus_coercions(
ArrayRef->of(Str)->where(q{ @$_ % 2 == 0 }), q{
my %h = @$_;
\%h;
},
),
coerce => 1, # never forget!
);
if you understand that, you are genuinely in the advanced class. congratulations!! ππ
π Unions π
sometimes you want to accept one thing or another thing. super easy with Type::Tiny.
use Types::Standard qw( HashRef ArrayRef Str );
has strings => (
is => 'ro',
isa => ArrayRef[Str] | HashRef[Str],
);
Type::Tiny overloads the bitwise or operator, so stuff like this should "just work".
that said β and this is the important part β now any code that calls $self->strings will probably need to check whether the value is an arrayref or a hashref before doing anything with it. so honestly it may be simpler overall if you just pick one of the options and coerce the other one into it. π
π Intersections π
similar to a union is an intersection.
package MyAPI::Client {
use Moo;
use Types::Standard qw( HasMethods InstanceOf );
has ua => (
is => 'ro',
isa => (InstanceOf["MyUA"]) & (HasMethods["store_cookie"]),
);
}
here we're checking that the UA is an instance of the MyUA class and also offers the store_cookie method. maybe store_cookie isn't provided by the MyUA class itself, but several subclasses of MyUA provide it. teamwork π
intersections are not useful as often as unions are, though. that's because they often make no sense. (ArrayRef) & (HashRef) would be a reference simultaneously pointing to an array and a hash, which is literally impossible. π
also note: when using intersections, it is good practice to put parentheses around each type. this is to disambiguate the meaning of & for Perl, because Perl uses it as the bitwise and operator but also as the sigil for subs.
π¦ Complements π¦
for any type Foo there's a complementary type ~Foo (pronounced "not Foo").
package My::Class {
use Moo;
use Types::Standard qw( ArrayRef CodeRef );
has things => ( is => 'ro', isa => ArrayRef[~CodeRef] );
}
so things is now an arrayref of anything except coderefs. iconic.
and if you need a number that is not an integer:
Num & ~Int
Types::Standard includes two types that are complements of each other: Undef and Defined. π«Ά
now, NegativeInt might seem like the complement of PositiveOrZeroInt, but when you actually think about it, it's not. there are values that fall into neither category β non-integers, non-numeric strings, references, undef, etc. so no, bestie, they're not opposites.
π "stringifies_to" and "numifies_to" π
the Object type constraint provides stringifies_to and numifies_to methods, and honestly examples explain them best.
Object->numifies_to(Int) means any object where 0 + $object is an integer.
Object->stringifies_to(StrMatch[$re]) means any object where $object matches the regular expression.
Object->stringifies_to($re) also works as a shortcut.
Object->numifies_to($coderef) and Object->stringifies_to($coderef) also work, where the coderef checks $_ and returns a boolean.
other types that are also logically objects β like parameterized HasMethods, InstanceOf, and ConsumerOf β should also provide stringifies_to and numifies_to methods.
stringifies_to and numifies_to work on unions if all of the type constraints in the union offer the method.
stringifies_to and numifies_to work on intersections if at least one of of the type constraints in the intersection offers the method.
πΈ "with_attribute_values" πΈ
another one that's best explained with an example:
package Horse {
use Types::Standard qw( Enum Object );
has gender => (
is => 'ro',
isa => Enum['m', 'f'],
);
has father => (
is => 'ro',
isa => Object->with_attribute_values(gender => Enum['m']),
);
has mother => (
is => 'ro',
isa => Object->with_attribute_values(gender => Enum['f']),
);
}
in this example, when you set a horse's father, it will call $father->gender and check that it matches Enum['m']. vibe check on the dad, basically. π΄
this method is in the same family as stringifies_as and numifies_as, so like those, it only applies to Object and similar type constraints, it can work on unions/intersections under the same circumstances, and it will also accept coderefs and regexps.
has father => (
is => 'ro',
isa => Object->with_attribute_values(gender => sub { $_ eq 'm' }),
);
has mother => (
is => 'ro',
isa => Object->with_attribute_values(gender => qr/^f/i),
);
all of stringifies_as, numifies_as, and with_attributes_as are really just wrappers around where. these following two are roughly equivalent:
my $type1 = Object->with_attribute_values(foo => Int, bar => Num);
my $type2 = Object->where(sub {
Int->check( $_->foo ) and Num->check( $_->bar )
});
but the first one results in better performing code though. so like, pick the first one. π
π Tied Variables π
it is possible to tie variables to a type constraint. yes really.
use Types::Standard qw(Int);
tie my $n, Int, 4;
print "$n\n"; # says "4"
$n = 5; # ok
$n = "foo"; # dies
you can also tie arrays:
tie my @numbers, Int;
push @numbers, 1 .. 10;
and hashes:
tie my %numbers, Int;
$numbers{lucky} = 7;
$numbers{unlucky} = 13;
earlier in the manual, it was mentioned that there's a problem with code like this:
push $horse->children->@*, $non_horse;
this can be solved using tied variables.
tie $horse->children->@*, InstanceOf["Horse"];
here's a longer example using builders and triggers:
package Horse {
use Moo;
use Types::Standard qw( Str Num ArrayRef InstanceOf );
use Type::Params qw( signature_for );
use namespace::autoclean;
my $ThisClass = InstanceOf[ __PACKAGE__ ];
has name => ( is => 'ro', isa => Str );
has gender => ( is => 'ro', isa => Str );
has age => ( is => 'rw', isa => Num );
has children => (
is => 'rw',
isa => ArrayRef[$ThisClass],
builder => "_build_children",
trigger => sub { shift->_trigger_children(@_) },
);
# tie a default arrayref
sub _build_children ( $self ) {
tie my @kids, $ThisClass;
\@kids;
}
# this method will tie an arrayref provided by the caller
sub _trigger_children ( $self, $new ) {
tie $new->@*, $ThisClass;
}
signature_for add_child => (
method => $ThisClass,
positional => [ $ThisClass ],
);
sub add_child ( $self, $kid ) {
push $self->children->@*, $kid;
return $self;
}
}
now it's pretty much impossible for the caller to make a mess by adding a non-horse as a child. the vibes are protected. π‘οΈπ
(note there's a Types::Self module on CPAN that will define a Self type meaning InstanceOf[ __PACKAGE__ ] for you! go grab it, bestie.)
π NEXT STEPS π
here's your next step:
Type::Tiny::Manual::UsingWithMoo3β there's more than one way to do it! alternative ways of using Type::Tiny, including type registries, exported functions, anddwim_type.
π AUTHOR π
Toby Inkster <tobyink@cpan.org>. π
π COPYRIGHT AND LICENCE π
okay so legal tea time, bestie, but make it cute πΈ
This software is copyright (c) 2013-2014, 2017-2025 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. π«Ά
no cap, that's the whole licence β same terms as Perl 5 itself, so if you know Perl's licence you already know this one β¨
π¨ DISCLAIMER OF WARRANTIES π¨
listen. i need you to hear me on this one, fr fr π
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.
that's the dramatic all-caps part for a reason, bestie β no promises, no guarantees, it is what it is π
and that's a wrap on Type::T...WithMoo2(3) π this bad boy is perl v5.42.0, dated 2026-01-17, and it ate, it devoured, it left no crumbs π go forth and build your types, you absolute icon ππ¦
π¦ where this page comes from
- fedora 44: perl-Type-Tiny 2.010001-2.fc44 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms