类型约束 'XYZ' 已创建

The type constraint 'XYZ' has already been created

我想在我的应用程序中使用 Moose::Util::TypeConstraints

所以我在 main.pl

中定义了一个

main.pl

use Moose::Util::TypeConstraints;

subtype 'mySpecialType'
    => as 'Object'
    => where sub { $_->does('something') };

use noUse;

noUse.pm中使用的包,使用类型约束

noUse.pm

package noUse;

use Use1;

use Use2;

1;

和我的包 Use1Use2 正在使用类型约束

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;

如果我 运行 main.pl 我得到这个错误:

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

是什么原因导致此错误,我该如何解决?

此答案调查错误发生的原因

首先,我们需要记住 Moose 的类型约束是全局的。 Moose 本身会跟踪它们。

Moose 似乎自动从 isa => 'Foo' 生成 Moose::Util::TypeConstraints。

我将 main.pl 中的代码更改为以下 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;

原来是这样。

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
    }
}

这是一个简单的 isa 检查,如果不存在具有该名称的其他约束,Moose 似乎会进行检查。这样,你就可以做这样的事情了。

use Moose;
has date => ( is => 'ro', isa => 'DateTime' );

我们原来的 main.pl 中的执行顺序是问题所在,因为 use 编译时完成 [=131] =].

这里又是你的错误,重点是。

type constraint 'mySpecialType' has already been created in Use1 and cannot be created again in main

让我们看看当main.pl得到运行时发生了什么。我在下面的分析中忽略了 Moose 本身和其他模块,这是高度简化的。

  • 编译时main.pl第1行use Moose::Util::TypeConstraints;
    • 那个模块被加载,发生了一堆事情,我们忽略那些
  • 编译时main.pl第6行use noUse;
    • 编译时noUse.pm第2行use Use1;
      • 编译时Use1.pm第2行use Moose;
        • Moose 加载,发生了很多事情,我们忽略那些
      • 运行 time: Use1.pm line 3 has 'object1' ... puts attribute object1 变成 Use1 class。此时 Moose 检查是否存在 mySpecialType 的类型约束,然后使用 isa 检查,因为它假设这是一个包名称。
    • 编译时noUse.pm第3行use Use2;
      • 编译时Use2.pm第2行use Moose;
        • Moose 加载,发生了很多事情,我们忽略那些
      • 运行 time: Use2.pm line 3 has 'object2' ... puts attribute object2 变成 Use2 class。此时 Moose 检查是否存在 mySpecialType 的类型约束并且存在(因为它是在 Use1.pm 中创建的)。
  • 运行 time: main.pl line 2ff subtype 'mySpecialType' ... 尝试创建类型约束mySpecialType。这失败了,因为已经有一个。它创建于 Use1.pm。砰。

所以问题是 main.plused 的包中的代码在 运行 中的正常代码之前得到 运行 =81=]main.pl。您需要在 运行 时间 第一次遇到它们之前声明您的类型。

让我们通过一个肮脏的 hack 来证明这一点。

use Moose::Util::TypeConstraints;

BEGIN {
    subtype 'mySpecialType' => as 'Object' => where sub { $_->does('something') };
}

use noUse;

这不会爆炸,因为 BEGIN 块在 编译时 得到 运行,这是在 use noUse 语句之前。因此,当 Moose 在 has => ( ... isa => 'mySpecialType' ) 构造中第一次遇到它时,类型约束已经存在。

但这并不漂亮。让我们尝试另一种解决方案。 使用 Moose::Util::TypeConstraints;

subtype 'mySpecialType' => as 'Object' => where sub { $_->does('something') };

require noUse;

这也会起作用,因为 require 不是在 编译时 调用,而是在 运行 时 [=131] =].所以先安装 subtype,然后加载 noUse。但这会延迟加载所有 classes,直到我们已经在您的主程序中进入 运行 时间,这是错误的形式。

他们这样做的正确方法 是将所有类型放入类型库,然后先加载那个。请参阅 了解如何操作。

为了更好地衡量,您还应该 use 在使用任何类型的所有模块中使用该类型库模块。这样您就可以确保将来,当您使用相同的 classes 编写其他应用程序时,您不需要记住加载该类型库。 始终 use class 所需的一切 class。

标准的做法是use需要的库中的公共代码,而不是让它从主程序中辐射出来。

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;

main.pl 变成

use noUse;

noUse.pm保持不变。

请参阅 simbabque's 了解原因。