The type constraint 'XYZ' has already been

2019-07-30 01:51发布

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?

标签: perl types moose
2条回答
仙女界的扛把子
2楼-- · 2019-07-30 02:12

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 used 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.

查看更多
相关推荐>>
3楼-- · 2019-07-30 02:18

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.

查看更多
登录 后发表回答