从Perl 6中的异常处理程序返回值

Sea*_*ean 9 resume exception try-catch perl6 raku

我一直在尝试编写一个执行以下逻辑的Perl 6表达式:计算子表达式并返回其值,但如果这样做会导致引发异常,请捕获异常并返回固定值.

例如,假设我想要分割两个数字,并且-1如果发生错误,则将表达式求值为.在Ruby中我可能会写:

quotient = begin; a / b; rescue; -1; end
Run Code Online (Sandbox Code Playgroud)

在Emacs Lisp中可能写成:

(setq quotient (condition-case nil (/ a b) (error -1))
Run Code Online (Sandbox Code Playgroud)

我的第一次Perl 6尝试是这样的:

sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };
Run Code Online (Sandbox Code Playgroud)

$quotient无论是否$b为零,这里最终都是未定义的.

似乎CATCH忽略了返回的值,或者至少在描述异常如何工作的doc页面上,所有CATCH主体仅执行具有副作用的事情,例如日志记录.

该页面提到try了另一种选择.我可以写一些例子:

my $quotient = try { might-throw($a, $b) } // -1;
Run Code Online (Sandbox Code Playgroud)

我觉得这是一个相当平庸的解决方案.首先,我正在评估的表达式可能真的有一个未定义的值,我无法将其与抛出异常的情况区分开来.另一方面,我可能希望根据抛出的异常的类别回退到不同的值,但try只是吞下它们.我可以将自己的CATCH块放在try区分异常中,但后来我回到上面的第一种情况,其中CATCH忽略了来自的值.

Perl 6的异常处理可以像我所表达的那样,我希望它能够在上面做吗?

编辑:

当前的答案是提供信息的,但是过于狭隘地关注除法运算符的语义.我稍微改写了这个问题,使异常的主要问题更加重要.

Sci*_*mon 6

你的catch块不起作用的原因是因为除以零并不是一个错误.Perl6很乐意让你除以零并将该值存储为Rat.当你想以有用的方式显示所述鼠标时(IE浏览器say)会出现问题.当你得到一个失败的时候,如果没有处理就会变成和异常.

所以你有几个选择.你可以$b在做之前检查$q:

$q = $b == 0 ?? -1 !! $a / $b; 
Run Code Online (Sandbox Code Playgroud)

或者,如果你想保持真实值(注意你可以反省鼠的分子和分母而不会导致除零错误),当你say使用它时,你可以使用.perl.Num版本.

当你有分母时,两者都给出Rat.perl给出<1/0>.Num给出的十进制表示.Inf0


rai*_*iph 5

TL;DR本答案的最后一部分讨论了您尝试中发生的其他人未能解决的问题[1 2]。其余部分介绍trys了一个全面解决您的 Q 所展示的整体问题的解决方案。

trys 概括

几个非常简单的例子:

say trys { die }, { -1 }                          # -1

say trys { die }, { when X::AdHoc { 42 } }        # 42
Run Code Online (Sandbox Code Playgroud)

trys

  • 不是拼写错误。[3]

  • 获取一个或多个Callables(函数、lambda 等)的列表。

  • 将环境异常Callable作为其主题传递给每个。

  • trys 每个Callable轮流直到:

    • 所有“失败”(抛出异常或以其他方式拒绝结果)。如果是,则trys返回一个Failure包装了最后一个抛出的异常Callable(如果:$list-throws传递了optional则返回所有异常);

    或者

    • 一个成功。如果是,则trys返回该成功结果。

trys代码

unit module X2;

our sub trys ( **@blocks,                 #= List of code blocks.
               :$reject = (),             #= Value(s) to be rejected.
               :$HANDLED = True,          #= Mark `Failure` as handled?
               :$list-throws is copy      #= List *all* throws in final `Failure` payload?
                         = False,
             ) is export {

  $! = CLIENT::<$!>;                      # Set argument of first block to caller's `$!`.
  if $! and $list-throws                  # Include caller's `$!` in list of throws.
    { $list-throws = [].push: $! }        # (Reuse `$list-throws` for store. Why not? :))

  my $result is default(Nil);             # At least temporarily preserve a `Nil` result.

  for @blocks -> &block {
    $result = try { block $! }            # Try block with `$!` from previous try as topic.
    if not $! and $result ~~ $reject.any  # Promote result to exception.
      { $! = X::AdHoc.new: payload => "Rejected $result.gist()" }
    if $! and $list-throws
      { $list-throws .push: $! }
    return $result unless $!;             # Return result if block didn't throw.
  }

  if $list-throws
    { $! = X::AdHoc.new: payload => $list-throws }

  given Failure.new: $! {                 # Convert exception(s) to `Failure`.
    .handled = True if $HANDLED;
    .return
  }
}
Run Code Online (Sandbox Code Playgroud)

glot.io 上的代码(包括trys此答案中的所有代码)。

trys 详细

use X2;

# `trys` tries a list of callables, short circuiting if one "works":
say trys {die}, {42}, {fail}                  # 42

# By default, "works" means no exception thrown and result is not a `Failure`:
say trys {die}, {fail}, {42}                  # 42

# An (optional) `:reject` argument lets you specify
# additional value(s) you want rejected if they match via infix `~~`:
say trys :reject(Nil,/o/), {Nil}, {'no'}, {2} # 2

# If no callable works, the last error is converted into a `Failure` and returned:
say trys :reject(Nil), {Nil}                  # (HANDLED) Rejected Nil
say trys {die}                                # (HANDLED) Died
say trys {(42/0).Str}                         # (HANDLED) Attempt to divide by zero
# To stop last error `Failure`s being handled, specify optional argument `:!HANDLED`:
say (trys {(42/0).Str}, :!HANDLED) .handled;  # False

# The first callable is passed the caller's current exception as its topic:
$! = X::AdHoc.new: payload => 'foo';
trys {.say}                                   # foo

# Subsequent callables are passed the exception from the prior callable as their topic:
trys {die 'bar'}, *.say;                      # bar
trys {fail 'bar'}, {die "$_ baz"}, *.say;     # bar baz

# Unless there's an exception in the guts of `trys`, the caller's `$!` is left alone:
say $!;                                       # foo

# To make final `Failure` payload be a *list*, specify optional argument `:list-throws`:
say trys {die 'bar'}, :list-throws;           # (HANDLED) foo bar
# (`list-throws` includes the caller's original `$!` if it was defined.)
Run Code Online (Sandbox Code Playgroud)

trys “陷阱”

# Some "traps" are specific to the way `trys` works:

say trys { ... } // 42;                   # "(HANDLED) Stub code executed"
say trys { ... }, { 42 }                  # 42   <-- Do this instead.

#trys 22;                                 # Type check failed ... got Int (22)
#trys {}                                  # Type check failed ... got Hash ({})
say trys {;}                              # Nil   <-- Use blocks instead.

# Other "traps" are due to the way Raku works:

# Surprise `False` result if callable has `when`s but none match:
say do   {when rand { 42 }}               # False   <-- It's how Raku works.
say trys {when rand { 42 }}               # False   <-- So same with `trys`.
say trys {when rand { 42 }; Nil}          # Nil     <-- Succinct fix.
say trys {when rand { 42 }; default {}}   # Nil     <-- Verbose fix.

# Surprise `(Any)` result if callable's last/return value is explicitly `$!`:
$! = X::AdHoc.new: payload => 'foo';
say try {$!}                              # (Any)   <-- It's how Raku works.
say $!;                                   # (Any)   <-- Clears `$!` *before* return!
$! = X::AdHoc.new: payload => 'foo';
say trys {$_}                             # (Any)   <-- `trys` has same return behaviour.
say $!;                                   # foo     <-- (But doesn't clear caller's `$!`.)
$! = X::AdHoc.new: payload => 'foo';
say try {$!.self}                         # foo     <-- One way to fix with `try`.
say $!;                                   # (Any)   <-- (Still clears caller's `$!`.)
$! = X::AdHoc.new: payload => 'foo';
say trys {.self}                          # foo     <-- Suggested fix with `trys`.
say $!;                                   # foo     <-- (Doesn't clear caller's `$!`.)
Run Code Online (Sandbox Code Playgroud)

trysvs 内置tryCATCH

trys结合了选定的部件tryCATCH

  • 就像try它的可调用对象执行try他们的代码一样。但:

    • 支持声明的形式; trys 42不管用。

    • 它将异常转换为Failures

    • 它允许用户指定他们想要提升的值列表Failure

  • 就像CATCH每个可调用对象都传递一个异常作为其主题一样。但:

    • callables不是 移相器块;它们不会被自动调用。

    • 它返回一个值(与CATCH块不同)。

每个tryscallable 可以扮演一个try角色,一个CATCH角色,或两者兼而有之。

讨论你的尝试

我的第一次 Raku 尝试是这样的:

sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };
Run Code Online (Sandbox Code Playgroud)

一个CATCH块总是返回Nil。它是闭包体中的最后一条语句,所以Nil总是返回 a。(这是一个似乎应该修复的footgun。请参阅在不创建 GOTO 的情况下实际捕获异常中的进一步讨论)

我可能会写例如:

my $quotient = try { might-throw($a, $b) } // -1;
Run Code Online (Sandbox Code Playgroud)

我正在评估的表达式可能真的有一个未定义的值,我无法将其与抛出异常的情况区分开来。

你可以写:

my $quotient is default(-1) = try { might-throw($a, $b) }
Run Code Online (Sandbox Code Playgroud)

这里发生了什么:

  • is defaulttrait 声明了一个变量的默认值是什么,如果它没有被初始化,并且如果有尝试分配,就会使用它Nil。(虽然Nil技术上是一个未定义的值,但它的目的是表示“没有值或良性故障”。)

  • try被定义为Nil在评估期间抛出异常时返回。


如果想要区分Nil由于抛出异常而返回的 a 和由于 a 的普通返回而返回的 a ,这可能仍然不能令人满意Nil。或者,也许更重要的是:

我可能想根据抛出异常的类别回退到不同的值,但try只是将它们全部吞下。

这需要一个解决方案,但不是CATCH

我可以把我自己的CATCH块放在里面try来区分异常,但是我又回到了上面的第一种情况

相反,现在有trys我创建的函数。

脚注

[1]正如您所指出的:“当前的答案……过于狭隘地关注除法运算符的语义。”。所以我已经在这方面做了脚注,也就是说:为了支持高级数学,Raku 不会自动将有理数除以零(例如1/0)视为异常/错误。Raku 随之而来的双重延迟异常处理是一个红鲱鱼。

[2] CATCH也是红鲱鱼。它不返回值,也不注入值,即使与 一起使用.resume,因此它是完成需要完成的工作的错误工具。

[3]有些人可能认为trys最好拼写为tries。但我是故意拼写的trys。为什么?因为:

  • 在英语中,就该词tries与 相关的程度而言try,它的关系非常密切。这个词选择的奇怪之处trys是为了提醒人们它不仅仅是复数try。这就是说,粗糙的意义有所密切相关的try,所以它的拼写trys仍然是有意义的海事组织。

  • 我喜欢奇思妙想。显然,在阿尔巴尼亚语中,trys意思是“按压、压缩、挤压”。就像trytrys函数“按下”代码(“压力”意义上的“按下”),并“压缩”它(与不使用的冗长相比trys),并“挤压”所有与异常相关的错误机制 - - Exceptions, Failures, Nils, try, CATCH, .resume-- 合二为一。

  • 在立陶宛语中,trys意思是“三个”。trys

    1. 拒绝三种结果:Exceptions;Failures; 和用户指定的:reject值。

    2. 以三种方式使事情滚动:将调用者的传递$!给第一个可调用对象;以最后一个异常为主题调用后续可调用对象;将最后一个块中抛出的异常转换为Failure.

    3. 解决了编程中最难的事情之一——命名事物:trystry明显的方式相似但又不同;我在此预测很少有开发人员会trys在他们的代码中使用阿尔巴尼亚语或立陶宛语;选择trys而不是tries使其不太可能与现有代码发生冲突。:)


Håk*_*and 3

我有以下工作:

use v6;

my $a = 1;
my $b = 0;
my $quotient = $a / $b;
try {
    #$quotient;   # <-- Strangely, this does not work
    "$quotient"; 
    CATCH {
        when X::Numeric::DivideByZero {
            $quotient = -1;
        }
        default { fail }
    }
}
say "Value of quotient: ", $quotient;
Run Code Online (Sandbox Code Playgroud)

输出

Value of quotient: -1
Run Code Online (Sandbox Code Playgroud)

但是,如果我不在子句$quotient中进行字符串化try,则会给出

Useless use of $quotient in sink context (line 9)
Attempt to divide 1 by zero using div
  in block <unit> at ./p.p6 line 18
Run Code Online (Sandbox Code Playgroud)

我不确定这是否是一个错误..

编辑

解决块返回值的问题CATCH您可以通过调用resume方法来解决它不向外部作用域返回值的问题:

my $a = 1;
my $b = 0;
my $quotient = do {
    my $result = might-throw($a, $b);
    CATCH {
        default {
            say "Caught exception: ", .^name;
            .resume;
        }
    }
    $result;  #<-- NOTE: If I comment out this line, it does not work
              #          A bug?
};

sub might-throw($a, $b) {
    if $b == 0 {
        die "Zero";
        -1;  # <-- the resume method call from CATCH will continue here
    }
    else {
        $a / $b
    }
}
Run Code Online (Sandbox Code Playgroud)