为什么 Perl 6 会为我的子集类型抛出 X::AdHoc 异常?

Why does Perl 6 throw an X::AdHoc exception for my subset type?

这是 Perl 6 中报告的错误:X::AdHoc instead of X::TypeCheck::Binding with subset parameter,首次报告于 2015 年 11 月。


在玩我的 Perl 6 模块时 Chemisty::Elements, I've run into an Exception 出乎我意料的问题。

我定义了一个类型,ZInt,它将数字限制为周期表上的序数(我在这里伪造了一点)。然后我使用该类型将参数约束到子例程。我希望得到某种 X::TypeCheck, but I get X::AdHoc 而不是:

use v6;

subset ZInt of Cool is export where {
    state ( $min, $max ) = <1 120>;
    ( $_.truncate == $_ and $min <= $_ <= $max )
        or warn "Z must be between a positive whole number from $min to $max. Got <$_>."
    };

sub foo ( ZInt $Z ) { say $Z }

try {
    CATCH {
        default { .^name.say }
        }

    foo( 156 );
    }

首先,我收到两次警告,这很奇怪:

Z must be between a positive whole number from 1 to 120. Got <156>. in block at zint.p6 line 5 Z must be between a positive whole number from 1 to 120. Got <156>. in block at zint.p6 line 5 X::AdHoc

但是,当我希望人们知道这是一个类型错误时,我得到了 X::AdHoc 类型。

我检查了没有 warn 会发生什么,然后又得到了 X::AdHoc

subset ZInt of Cool is export where {
    state ( $min, $max ) = <1 120>;
    ( $_.truncate == $_ and $min <= $_ <= $max )
    };

所以,我想我可以抛出我自己的异常:

subset ZInt of Cool is export where {
    state ( $min, $max ) = <1 120>;
    ( $_.truncate == $_ and $min <= $_ <= $max )
        or X::TypeCheck.new.throw;
    };

但是,我收到警告:

Use of uninitialized value of type Any in string context Any of .^name, .perl, .gist, or .say can stringify undefined things, if needed.

此时我不知道在抱怨什么。我认为其中一种方法需要一些我没有提供的东西,但我在文档中没有看到任何关于 newthrow 的参数。

如何在没有警告的情况下获得我想要的类型以及我的自定义文本?

您可以通过 --ll-exception 并尝试弄清楚您最终是如何得到错误和消息的,但我不确定这会有多大帮助。

关于使用未初始化值的警告:您需要为X::TypeCheck.new提供一个命名的operation参数;您可以提供的其他参数是 gotexpected,请参见 core/Exception.pm.

然而,从子集声明中抛出是一个坏主意,因为针对该特定类型的任何智能匹配现在都会爆炸。一个稍微好一点的想法是 .fail 例外,但我仍然觉得不对:不属于子集类型并不是例外情况。

或者,您可以提供一个多候选人来完成死亡:

subset ZInt of Cool where $_ %% 1 && $_ ~~ 1..120;

proto foo($) {*}
multi foo(ZInt $Z) { say $Z }
multi foo($Z) {
    die X::TypeCheck.new(
        operation => 'foo',
        got => $Z,
        expected => ZInt
    );
}

如果您提供像 "hello" 这样的参数但在数字转换时失败,因为 %% 将抛出而不是传播失败,这仍然有问题,这可能被认为是 Rakudo 核心设置的缺陷.

您可以通过

之类的方式解决这个问题
subset ZInt of Cool where { try $_ %% 1 && $_ ~~ 1..120 }

subset ZInt of Cool where { .Numeric andthen $_ %% 1 && $_ ~~ 1..120 }

参数类型检查、子集或 where 子句、失败和异常的整个交互可能有些脆弱,因此您可能需要进行一些试验,直到获得您喜欢的语义和行为。

另一种方法是通过单独的范围检查从 Cool 强制转换为 Int

subset ZInt of Int where 1..120 ;

sub foo(Int(Cool) $Z where ZInt) {
    say $Z.perl;
}

在理想的世界中,应该有某种方式来表达这种强制类型约束,例如 ZInt(Cool)

不要抛出异常或发出警告。相反,你想失败:

subset ZInt of Cool is export where {
    state ( $min, $max ) = <1 120>;
    ( $_.truncate == $_ and $min <= $_ <= $max )
        or fail "Z must be between a positive whole number from $min to $max. Got <$_>."
};

我相信这是你的意图。因您自己的异常而失败也很好,但是 X::TypeCheck 中有一个错误。它应该要求 "operation" 或像 "got" 和 "expected".

那样提供合理的默认值
subset ZInt of Cool is export where {
    state ( $min, $max ) = <1 120>;
    ( $_.truncate == $_ and $min <= $_ <= $max )
    or fail X::TypeCheck.new(
            operation => "type check",
            expected  => ::('ZInt'),
            got       => $_,
        );
};