Moose(Perl):将undef转换为空字符串或0,而不是die()

8

由于我的Moose构造函数中存在不完整的数据,QA给了我很多异常反馈。虽然构造函数参数中存在属性名称,但属性值为undef

对于许多脚本应用程序来说,有些事情就是undef,这是很正常的现象。通常情况下这是没有问题的。您不希望从 pragma中收到烦人的警告(因此执行no warnings'未初始化'命令),并且肯定不希望您的代码因为一个小值(比如门牌号)而死亡undef

所以,毋庸赘言,我希望我的Moose构造函数行为与纯Perl相同(即不使用use warnings 'uninitialized'),即将undef转换为0或所需的空字符串。此示例中显示的尝试无法处理属性名称存在但值为undef的情况。我考虑使用BUILDARGS来实现我想要的结果。但是,在纯Moose中是否有一种声明性方法可以做到这一点,而不必使用MooseX::UndefTolerant(不幸的是,我无法使用它,因为它没有安装)?

package AAA;
use Moose;
has 'hu', is => 'ro', isa => 'Str';
has 'ba', is => 'ro', isa => 'Int';
no Moose; __PACKAGE__->meta->make_immutable;

package BBB;
use Moose; extends 'AAA';
has '+hu', default => ''; # don't want to die on undef
has '+ba', default => 0;  # idem
no Moose; __PACKAGE__->meta->make_immutable;

package main;
use Test::More;
use Test::Exception;
# Those AAAs should die ...
throws_ok { AAA->new( hu => undef ) }
    qr/Validation failed for 'Str' with value undef/;
throws_ok { AAA->new( ba => undef ) }
    qr/Validation failed for 'Int' with value undef/;
# .. but these BBBs should live:
lives_ok  { BBB->new( hu => undef ) } 'hu supplied as undef';
lives_ok  { BBB->new( ba => undef ) } 'ba supplied as undef';
done_testing;

3
据我所知,你不应该试图抑制警告。为什么不在之前检查值是否已定义?可以使用(defined($value))。此外,你可以将 MooseX::UndefTolerant 安装到本地目录 $my_dir 中,并使用 use lib($my_dir); - yarian
问题在于类型限制。当你说 foo isa Int,然后为 foo 提供一个未定义的值时,它会失败,因为 undef 不是 Int 类型。 - hobbs
@YGomez +1 不错的想法,我也许会这么做。 :-) 至于因未初始化值而引起的警告,请参阅 common::sense。我同意作者Marc Lehmann在他对未初始化值的讨论中的观点。(当然有例外情况,但是嘿-它们是例外)。预先检查值只是很繁琐的。我必须在许多地方重复检查,并尝试保持DRY - Lumi
1
常识不是常见的感觉。 - David Raab
3个回答

10

Moose::Manual::Types中记录了一种处理这种问题的方法。

使用Maybe[a]类型。

package AAA;
use Moose;

has 'hu', is => 'ro', isa => 'Str';
has 'ba', is => 'ro', isa => 'Int';

no Moose; __PACKAGE__->meta->make_immutable;


package BBB;
use Moose; extends 'AAA';

has 'hu', is => 'rw', isa => 'Maybe[Str]', default => ''; # will not die on undef
has 'ba', is => 'rw', isa => 'Maybe[Int]', default => 0;  # idem

sub BUILD {
    my $self = shift;
    $self->hu('') unless defined $self->hu;
    $self->ba(0) unless defined $self->ba;
}

no Moose; __PACKAGE__->meta->make_immutable;


package main;
use Test::More;
use Test::Exception;

# Those AAAs should die ...
throws_ok { AAA->new( hu => undef ) }
    qr/Validation failed for 'Str' with value undef/;
throws_ok { AAA->new( ba => undef ) }
    qr/Validation failed for 'Int' with value undef/;

# .. but these BBBs should live:
lives_ok  { BBB->new( hu => undef ) } 'hu supplied as undef';
lives_ok  { BBB->new( ba => undef ) } 'ba supplied as undef';

my $bbb = BBB->new( hu => undef, ba => undef );

is $bbb->hu, '', "hu is ''";
is $bbb->ba, 0, 'ba is 0';

done_testing;

谢谢,这个可行。undef 值不会自动转换为 0 和空字符串,但我想我只能忍受它了(或者编写自己的 MooseX::UndefUpgrade 扩展)。 - Lumi
1
为了在传递 undef 时设置一个值,您可以使用 BUILD 子例程,在通过 BBB->new 创建对象时调用。相应地更新代码。但是,您必须设置属性 'rw' - matthias krull
现在我不知道Moose内部是如何排序的(我知道,开源...),但我可能不是唯一认为它会受益于赋予属性声明者(has)一个onundef属性。看起来你可以说onundef的默认行为是croak(或confess)。但是为什么不这样写呢?has 'something',is => 'ro',isa => 'Str',onundef => ''Class::MOP::Attributedefaultinitializer,但都不适用于处理undef的特殊情况。 - Lumi
我对此持不同意见。我认为API应该保持尽可能窄的范围。类型系统包含了Undef,可以像其他类型一样进行验证和处理。如果在构造期间未设置属性,则默认值/回调将会生效。如果设置了它,就不会生效。这是一致的行为,不应为一种类型或另一种类型而改变。如果需要操纵传入的数据,则仍然可以使用BUILD。 - matthias krull

4
你的抱怨实际上是 Moose 正在按照它应该做的方式行事。如果你明确地将 undef 作为值传递,但该值只能是 Int,则你应该会收到错误信息。
所以你需要做出选择。你可以通过联合来改变类型,使 undef 成为一个有效的值,就像这样:
    has 'hu', is => 'ro', isa => 'Str | Undef';
    has 'ba', is => 'ro', isa => 'Int | Undef';

或者您可以不发送未定义的值:

    my %aa_params = ();
    $aa_params{hu} = $foo if defined $foo;

    $aa = AA->new( %aa_params );

或者最后,如果由于某些未知原因您无法抵制发送无效的未定义值以设置为未定义的内容,请编写一个快速过滤器:
    sub filt_undef {
      my %hash = @_;
      return map { $_ => $hash{$_} } grep { defined $hash{$_} } keys %hash;
    }

    $aa = AA->new( filt_undef( hu => undef ) );

但这似乎相当笨拙和糟糕。

1
你的建议使用联合类型也可以,但是Maybe解决方案更符合undef问题的惯用表达,因此我认为这个方案更值得推荐。 - Lumi

3

或者使用即时强制转换:

package BBB;
use Moose;
use MooseX::AttributeShortcuts;
extends 'AAA';
has '+hu',
  traits => [Shortcuts],
  coerce => [ Undef => sub { '' } ],
;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接