Constraint of type "XYZ" has already been created
I want to use 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;
The package noUse.pm
uses packages that use a constraint like
noUse.pm
package noUse;
use Use1;
use Use2;
1;
and my package Use1
and Use2
work with a constraint like
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:
A constraint of type 'mySpecialType' has already been created in Use1 and cannot be re-created in main at / usr / lib / x 86_64-linux-gnu / perl5 / 5.22 / Moose / Util / TypeConstraints.pm line 348 Moose :: Util :: TypeConstraints :: subtype ('mySpecialType', 'HASH (0x227f398)', 'HASH (0x2261140)') called on line main.pl 10
What is causing this error and how can I fix it?
source to share
The standard way is to use
share code from libraries where you need it, rather than letting it emit 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
it becomes simple
use noUse;
noUse.pm
remains unchanged.
See simbabque's answer for what.
source to share
This answer investigates why the error occurs
First of all, we must remember that constraints like Moose are global. Moose himself tracks them.
Moose seems to automatically make Moose :: Util :: TypeConstraints from isa => 'Foo'
.
I changed my code in main.pl to the following to check if this limitation exists .
use noUse;
use Data::Printer { deparse => 1 };
my $type = Moose::Util::TypeConstraints::find_type_constraint('mySpecialType');
p $type;
It turns out it does it.
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 check isa
that seems to show up in Moose if no other restriction already exists with that name. So you can do things like this.
use Moose;
has date => ( is => 'ro', isa => 'DateTime' );
The order of execution in our original main.pl is a problem because it use
is executed at compile time.
Here's your mistake again, with emphasis.
constraint of type 'mySpecialType' has already been created in Use1 and cannot be created again in main
Let's see what happens when main.pl starts up. I am ignoring Moose itself and other modules in the analysis below, which is very simplistic.
- compile time: main.pl line 1
use Moose::Util::TypeConstraints;
- this module is loaded, a lot 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 is loading, a lot of things are happening, we ignore these
- runtime: Use1.pm line 3
has 'object1' ...
puts an attributeobject1
on a classUse1
. At this point, Moose checks to see if a type constraint exists formySpecialType
, and then does it with a checkisa
, because that means it's a package name.
- compile time: Use1.pm line 2
- compile time: noUse.pm line 3
use Use2;
- compile time: Use2.pm line 2
use Moose;
- Moose is loading, a lot of things are happening, we ignore these
- runtime: Use2.pm line 3
has 'object2' ...
puts an attributeobject2
on a classUse2
. At this point, Moose checks to see if the type constraint formySpecialType
and is (because it was created inUse1.pm
).
- compile time: Use2.pm line 2
- compile time: noUse.pm line 2
- runtime: main.pl line 2ff
subtype 'mySpecialType' ...
tries to create a type constraintmySpecialType
. This fails because he is no longer there. It was created inUse1.pm
. BOOM.
So the problem is that the code in the use
d packages in main.pl runs before the normal code in main.pl. You need to declare your types before they are encountered at runtime the first time.
Prove it with a dirty hack.
use Moose::Util::TypeConstraints;
BEGIN {
subtype 'mySpecialType' => as 'Object' => where sub { $_->does('something') };
}
use noUse;
It won't blow up because the block is BEGIN
run at compile time, which is before the statement use noUse
. Therefore, the type constraint will already be there when Moose first encounters it in a design has => ( ... isa => 'mySpecialType' )
.
But this is not very good. try another solution. use Moose :: Util :: TypeConstraints;
subtype 'mySpecialType' => as 'Object' => where sub { $_->does('something') };
require noUse;
This will also work because it is require
not called at compile time, but at run time. Therefore, it is first installed subtype
and then loaded noUse
. But this delays loading all your classes until we have finished your main program, which is bad form.
The correct way to do this is to put all your types in a type library and load them first. See choroba's answer for how to do this.
For good measure, you should also use
create a library module in all modules that use any of the types. This way, you ensure that in the future, when you write other applications with the same classes, you don't need to forget to load a library of that type. Always use
everything you need for a class in that class.
source to share