I'm trying to get deep coercions work with Type::Tiny
without any success. From the manual it's said that:
"Certain parameterized type constraints can automatically acquire
coercions if their parameters have coercions. For example:
ArrayRef[Int->plus_coercions(Num, q{int($_)}) ]
... does what you mean!"
What I try to accomplish is getting something like this "do what I mean":
package Person;
use Types::Standard -types;
use Moo;
has name => (is => 'ro', isa => Str);
package Family;
use Types::Standard -types;
use Moo;
has members => (is => 'ro', isa => ArrayRef[InstanceOf['Person']]);
package main;
my $family = Family->new(members => [
'mom',
Person->new(name => 'dad'),
Person->new(name => 'girl'),
'dog'
]);
When instantiating Family
with elements that are a Str
they should be automatically be coerced into Person
objects. I've tried a range of different ideas (plus_coercions, Type libraries, etc) without any luck. They all fail in the same way.
When using plus_coercions (from Str
to Object
)
package Family;
has members => (
is => 'ro',
isa => ArrayRef[ Object->plus_coercions(Str, q{ Person->new(name => $_) }) ],
);
Type::Tiny throws an exception:
Reference ["mom",bless( {"name" => "dad"}, 'Person' ),bless( {"name" =...] did not pass type constraint "ArrayRef[Object]" (in $args->{"members"})
"ArrayRef[Object]" constrains each value in the array with "Object"
"Object" is a subtype of "Object"
"Object" is a subtype of "Ref"
Value "mom" did not pass type constraint "Ref" (in $args->{"members"}->[0])
"Ref" is defined as: (!!ref($_))
I know I could get around this by modifying the arguments to Family->new
using a BUILDARGS
sub in Family
, but it would be neat if Type::Tiny could do that automatically.
Update
Thanks to Tobys friendly help, I got this working. The only part that troubled me a bit was the use of ArrayRef[Object]
instead of the correct ArrayRef[InstanceOf['Person']]
(InstanceOf
doesn't have any plus_coercions
). With Object
an instance of any class could have been inserted into members
, and that is certainly not what you want.
Got around that by making a class_type
. Here's the full working code:
package Person;
use Types::Standard -types;
use Moo;
has name => (is => 'ro', isa => Str);
package Family;
use Types::Standard -types;
use Type::Utils -all;
use Moo;
my $Person = class_type { class => 'Person' };
my $Members = ArrayRef[
$Person->plus_coercions(Str, q{ Person->new(name => $_) })
];
has members => (
is => 'ro',
isa => $Members,
coerce => $Members->coercion,
);
sub list { join(', ', map { $_->name } @{ $_[0]->members }) }
package main;
my $family = Family->new(members => [
'mom',
Person->new(name => 'dad'),
Person->new(name => 'girl'),
'dog'
]);
print $family->list, "\n";
Which nicely prints mom, dad, girl, dog
when run.
Moose/Moo/Mouse attributes don't coerce by default, so even though the type constraint has a coercion, you need to tell the attribute to use that coercion!
If you were using Moose or Mouse, you could do:
has members => (
is => 'ro',
isa => ArrayRef[ Object->plus_coercions(Str, q{ Person->new(name => $_) }) ],
coerce => 1,
);
But Moo doesn't support coerce=>1
; instead it expects a coderef or overloaded object to act as the coercion. Type::Tiny can provide you with a suitable overloaded object by calling $type->coercion
.
# Let's store the type constraint in a variable to avoid repetition...
my $type = ArrayRef[
Object->plus_coercions( Str, q{Person->new(name => $_)} )
];
has members => (
is => 'ro',
isa => $type,
coerce => $type->coercion,
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With