I want to use a Moose::Util::TypeConstraints
in my application.
So I define one in my main.pl
main.pl
use Moose::Util::TypeConstraints;
subtype 'mySpecialType'
=> as 'Object'
=> where sub { $_->does('something') };
use noUse;
In the package noUse.pm
are packages used, which use the type constraint
noUse.pm
package noUse;
use Use1;
use Use2;
1;
and my package Use1
and Use2
are working with the type constraint
Use1.pm
package Use1;
use Moose;
has 'object1' => ( is => 'ro', isa => 'mySpecialType' );
1;
Use2.pm
package Use2;
use Moose;
has 'object2' => ( is => 'ro', isa => 'mySpecialType' );
1;
If I run main.pl
I get this error:
The type constraint 'mySpecialType' has already been created in Use1 and cannot be created again in main at /usr/lib/x86_64-linux-gnu/perl5/5.22/Moose/Util/TypeConstraints.pm line 348
Moose::Util::TypeConstraints::subtype('mySpecialType', 'HASH(0x227f398)', 'HASH(0x2261140)') called at main.pl line 10
What causes this error and how can I fix it?
The standard way is to use
the common code from the libraries where it's needed, not to let it radiate from the main program.
MyTypes.pm
package MyTypes;
use Moose::Util::TypeConstraints;
subtype 'mySpecialType'
=> as 'Object'
=> where sub { $_->does('something') };
Use1.pm
package Use1;
use Moose;
use MyTypes;
has 'object1' => ( is => 'ro', isa => 'mySpecialType' );
1;
Use2.pm
package Use2;
use Moose;
use MyTypes;
has 'object2' => ( is => 'ro', isa => 'mySpecialType' );
1;
And main.pl
becomes just
use noUse;
noUse.pm
stays unchanged.
See simbabque's answer for why.
This answer investigates why the error happens
First of all, we need to remember that Moose's type constraints are global. Moose itself keeps track of them.
It seems that Moose automatically makes Moose::Util::TypeConstraints from isa => 'Foo'
.
I changed your code in main.pl to the following, to check if this constraint already exists.
use noUse;
use Data::Printer { deparse => 1 };
my $type = Moose::Util::TypeConstraints::find_type_constraint('mySpecialType');
p $type;
Turns out it does.
Moose::Meta::TypeConstraint::Class {
Parents Moose::Meta::TypeConstraint
public methods (9) : class, create_child_type, equals, get_message, is_a_type_of, is_subtype_of, meta, new, parents
private methods (1) : _new
internals: {
class "mySpecialType",
compiled_type_constraint sub {
package Eval::Closure::Sandbox_150;
use warnings;
use strict;
do {
$_[0]->isa('mySpecialType') if &Scalar::Util::blessed($_[0])
};
},
constraint sub {
package Moose::Meta::TypeConstraint::Class;
use warnings;
use strict;
$_[0]->isa($class_name);
},
_default_message sub {
package Moose::Meta::TypeConstraint;
use warnings;
use strict;
my $value = shift();
my $can_partialdump = try(sub {
require Devel::PartialDump;
'Devel::PartialDump'->VERSION(0.14);
1;
}
);
if ($can_partialdump) {
$value = 'Devel::PartialDump'->new->dump($value);
}
else {
$value = defined $value ? overload::StrVal($value) : 'undef';
}
return q[Validation failed for '] . $name . "' with value $value";
},
inline_environment {},
inlined sub {
package Moose::Meta::TypeConstraint::Class;
use warnings;
use strict;
my $self = shift();
my $val = shift();
return 'Scalar::Util::blessed(' . $val . ')' . ' && ' . $val . '->isa(' . B::perlstring($self->class) . ')';
},
name "mySpecialType",
package_defined_in "Use1",
parent Moose::Meta::TypeConstraint
}
}
This is a simple isa
check that Moose seems to put in if no other constraint already exists with that name. That way, you can do stuff like this.
use Moose;
has date => ( is => 'ro', isa => 'DateTime' );
The order of execution in our original main.pl is the problem, because use
gets done at compile time.
Here is your error again, with emphasis.
type constraint 'mySpecialType' has already been created in Use1 and cannot be created again in main
Let's take a look what is going on when main.pl gets run. I am ignoring Moose itself and other modules in the below analysis, which is highly simplified.
- compile time: main.pl line 1
use Moose::Util::TypeConstraints;
- that module gets loaded, a bunch of things happen, we ignore those
- compile time: main.pl line 6
use noUse;
- compile time: noUse.pm line 2
use Use1;
- compile time: Use1.pm line 2
use Moose;
- Moose gets loaded, a bunch of things happen, we ignore those
- run time: Use1.pm line 3
has 'object1' ...
puts the attribute object1
into the Use1
class. At this point Moose checks if there is a type constraint for mySpecialType
and then makes one with an isa
check because it asumes this is a package name.
- compile time: noUse.pm line 3
use Use2;
- compile time: Use2.pm line 2
use Moose;
- Moose gets loaded, a bunch of things happen, we ignore those
- run time: Use2.pm line 3
has 'object2' ...
puts the attribute object2
into the Use2
class. At this point Moose checks if there is a type constraint for mySpecialType
and there is (because it was created in Use1.pm
).
- run time: main.pl line 2ff
subtype 'mySpecialType' ...
tries to create a type constraint mySpecialType
. This fails, because there already is one. It was created in Use1.pm
. BOOM.
So the problem is that the code in packages that are use
d in main.pl gets run before the normal code in main.pl. You need to declare your types before they are encountered at run time the first time.
Let's prove this by doing a dirty hack.
use Moose::Util::TypeConstraints;
BEGIN {
subtype 'mySpecialType' => as 'Object' => where sub { $_->does('something') };
}
use noUse;
This will not blow up, because the BEGIN
block gets run at compile time, which is before the use noUse
statement. Therefore the type constraint will already be there when Moose encounters it the first time in a has => ( ... isa => 'mySpecialType' )
construct.
But that's not pretty. let's try another solution.
use Moose::Util::TypeConstraints;
subtype 'mySpecialType' => as 'Object' => where sub { $_->does('something') };
require noUse;
This will also work, because the require
is not called at compile time, but at run time. So the subtype
gets installed first, and then it will load noUse
. But this defers loading all your classes until we are already in run time in your main program, and that is bad form.
They correct way to do this is to put all your types into a type library and load that one first. See choroba's answer for how to do that.
For good measure, you should also use
that type library module in all modules that use any of the types. That way you ensure that in the future, when you write other applications with the same classes, you don't need to remember to load that type library. Always use
everything you need for a class in that class.