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?
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.
Turns out it does.
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.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.
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.
use Moose::Util::TypeConstraints;
use noUse;
use Use1;
use Moose;
has 'object1' ...
puts the attributeobject1
into theUse1
class. At this point Moose checks if there is a type constraint formySpecialType
and then makes one with anisa
check because it asumes this is a package name.use Use2;
use Moose;
has 'object2' ...
puts the attributeobject2
into theUse2
class. At this point Moose checks if there is a type constraint formySpecialType
and there is (because it was created inUse1.pm
).subtype 'mySpecialType' ...
tries to create a type constraintmySpecialType
. This fails, because there already is one. It was created inUse1.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.
This will not blow up, because the
BEGIN
block gets run at compile time, which is before theuse noUse
statement. Therefore the type constraint will already be there when Moose encounters it the first time in ahas => ( ... isa => 'mySpecialType' )
construct.But that's not pretty. let's try another solution. use Moose::Util::TypeConstraints;
This will also work, because the
require
is not called at compile time, but at run time. So thesubtype
gets installed first, and then it will loadnoUse
. 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. Alwaysuse
everything you need for a class in that class.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
Use1.pm
Use2.pm
And
main.pl
becomes justnoUse.pm
stays unchanged.See simbabque's answer for why.