【问题标题】:Why does Perl 6 throw an X::AdHoc exception for my subset type?为什么 Perl 6 会为我的子集类型抛出 X::AdHoc 异常?
【发布时间】:2016-10-18 00:55:49
【问题描述】:

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


在使用我的 Perl 6 模块 Chemisty::Elements 时,我遇到了一个我没想到的 Exception 问题。

我定义了一个类型,ZInt,它将数字限制为在周期图上找到的序数(我在这里做了一点伪造)。然后我使用该类型将参数约束到子例程。我希望得到某种X::TypeCheck,但我得到了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 必须介于 1 到 120 的正整数之间。得到 。在 zint.p6 第 5 行的块中 Z 必须介于 1 到 120 的正整数之间。得到 。在 zint.p6 第 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;
    };

但是,我收到警告:

在字符串上下文中使用 Any 类型的未初始化值 如果需要,任何 .^name、.perl、.gist 或 .say 都可以对未定义的内容进行字符串化。

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

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

【问题讨论】:

标签: exception types raku


【解决方案1】:

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

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 有一个错误。它应该要求“操作”或提供一个合理的默认值,就像它为“得到”和“预期”所做的那样。

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       => $_,
        );
};

【讨论】:

  • failwarn 的区别在于warn 立即响亮,而fail 只有在遇到其他代码时才会响亮,例如分配给例程返回后的变量。
  • fail 在这里是个好建议。我必须去找我复制的使用warn 的例子。 (我会将您的评论添加到答案中)。我仍然认为子集检查中的失败应该引发某种类型检查错误。我会看看如何修复 TypeCheck。
  • 我也曾在某处看到过该建议,但在我的快速搜索中找不到。
  • 唉,这最终将不起作用,因为它缩短了其他正确执行此操作的 TypeCheck 失败。我想我必须忍受X::AdHoc
【解决方案2】:

您可以通过 --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)

【讨论】:

  • 为此提供多订阅似乎是一种糟糕的编程方式。
  • @briandfoy:这不是一个完美的解决方案;我的观点是子集不应该抛出;从语义上讲,像ZInt(Cool) 这样的强制类型是正确的做法;问题在于它们是通过在输入值上调用 .ZInt 来实现的,而可以说它还应该在目标类型上寻找强制器;即使这是可能的,它也无助于您的问题,因为子集目前不能有方法,因为它们只是约束;我对使用 where 子句的另一种因式分解有一个想法,但我必须看看它是否有效......
  • @briandfoy:为我在理论上想做的事情添加了示例代码(但实际上可能不会)
  • @briandfoy:简化示例
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-28
  • 2013-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-08
相关资源
最近更新 更多