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

Exception 从Perl 6中的异常处理程序返回值,exception,try-catch,raku,resume,Exception,Try Catch,Raku,Resume,我一直在尝试编写一个Perl 6表达式,它执行以下逻辑:计算子表达式并返回其值,但如果这样做导致引发异常,则捕获异常并返回固定值 例如,假设我想将两个数字相除,如果发生错误,则表达式的计算结果为-1。我可以用Ruby写: quotient = begin; a / b; rescue; -1; end 在Emacs Lisp中,可以写成: (setq quotient (condition-case nil (/ a b) (error -1)) 我的第一次Perl 6尝试是这样的: sub

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

例如,假设我想将两个数字相除,如果发生错误,则表达式的计算结果为
-1
。我可以用Ruby写:

quotient = begin; a / b; rescue; -1; end
在Emacs Lisp中,可以写成:

(setq quotient (condition-case nil (/ a b) (error -1))
我的第一次Perl 6尝试是这样的:

sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };
但是在这里,
$quotient
最终是未定义的,不管
$b
是否为零

看来,
CATCH
返回的值被忽略了,或者至少在描述异常如何工作的上,所有的
CATCH
主体只做一些有副作用的事情,比如日志记录

该页面提到了
try
作为替代方案。例如,我可以写:

my $quotient = try { might-throw($a, $b) } // -1;
我觉得这是一个相当令人失望的解决办法。首先,我正在计算的表达式可能确实有一个未定义的值,我无法将其与引发异常的情况区分开来。另一方面,我可能希望根据抛出的异常的类别返回不同的值,但是
try
只是将它们全部吞下。我可以将我自己的
CATCH
块放在
try
中,以区分异常,但是我回到上面的第一个案例,其中忽略了
CATCH
中的值

Perl 6的异常处理能像我在上面所说的那样吗

编辑:


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

我做了以下工作:

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;
但是,如果我在
try
子句中没有将
$quotient
字符串化,它会给出

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

catch块不起作用的原因是被零除本身不是一个错误。Perl6将很高兴地让您除以零,并将该值存储为Rat。当您希望以有用的方式显示所述Rat(即
say
it)时,会出现问题。当您返回一个失败时,如果不进行处理,它将变成异常

所以你有几个选择。在进行
$q
之前,您可以检查
$b

$q = $b == 0 ?? -1 !! $a / $b; 
或者,如果您想保留真实值(注意,您可以在不导致被零除错误的情况下内省Rat的分子和分母),当您
时,您可以使用
.perl
.Num
版本


当分母为
0
时,这两种方法都为您提供了
Rat
的十进制表示形式,分别为
.perl
.Num
提供
Inf

这似乎是设计和/或实施缺陷:

Rakudo愉快地将一个
Int
除以
0
,返回一个
Rat
。您可以
.Num
它(产生
Inf
)和
.perl
它,但是如果您尝试
.Str
它,它会爆炸

相反,除以
Num
0e0
将立即失败


为了保持一致性,整数除以零可能也会失败。另一种方法是返回一个在字符串化时不会爆炸的常规值,但我反对它…

所以我们有一个函数。有时它返回
Any
(未定义)另一种方式是返回
$a/$b
,除非
$b
0
,在这种情况下它抛出异常

sub might-throw($a, $b) { 
    return Any if (True, False, False, False, False).pick();
    die "Zero" if $b == 0;  
    $a / $b;
}
我们希望商是函数调用的值,除非它抛出异常,在这种情况下,我们希望-1

让我们随机配对20对,然后进行尝试:

for 1..20 {
    my $a = (0..2).pick;
    my $b = (0..2).pick;
    my $quotient = -1;
    try {
        let $quotient = might-throw($a, $b);
        $quotient ~~ Any|Numeric;
    }
    say "{$a}/{$b} is {$quotient} maybe..";
}

因此,我们开始预定义错误状态的商。然后在一个try块中,我们使用
let
调用函数进行设置。如果函数错误或块返回未定义,则将回滚
let
。。。因此,我们测试
$商
任何
数值

TL;DR除了最后一部分之外,本答案的所有部分都包含了一个解决方案。最后一节讨论您尝试的解决方案

catch
描述
catch
是/做什么 我创建了一个新函数
catch

my $result = catch { ... }, { ... }, ...
multi catch ( *@blocks,           #= Assumes blocks, and only blocks, passed.
              :$handle = True,    #= If `Failure` returned, return it handled?
              :nil(:$Nil) = True  #= Is `Nil` to be considered an okay result?
            ) {

  # Grab caller's current lexical exception value
  $! = CLIENT::CALLER::CALLER::CALLER::<$!>; # No idea if this is reliable.
  
  my $result is default(Nil);       # `default(Nil)` to preserve `Nil` result

  for @blocks -> &block {
    $result = try { block $! }
    next if $result === Nil and !$Nil;
    return $result unless $!;       # Block didn't throw; return result
  }

  try { die 'Nil returned' } unless $! or $Nil;

  given Failure.new: $! {           # Last block threw exception; return `Failure`
    .handled = True if $handle;
    .return
  }
}
catch
组合了
try
catch
的选定部分:

  • catch
    类似于
    try
    (其块执行其代码的
    try
    ),但是:

    • 不支持语句形式--
      catch 22
      将不起作用

    • 让开发人员更方便地指定异常处理/

  • catch
    类似于
    catch
    (将当前异常作为主题传递给
    catch
    块),但是:

    • 不会自动调用(它不是一个)

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

  • 每个
    catch
    块要么扮演
    try
    角色,要么扮演
    catch
    角色,要么同时扮演这两个角色

catch
动作示例 涉及捕获的意外/错误
我将跳过进一步解释代码,除非在注释中要求这样做

我在脚注中添加了验证
catch
参数的代码。1


这个答案的其余部分讨论了您尝试做
catch
所做的事情


讨论你的尝试 我的第一次尝试是这样的:

sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };
CATCH
块始终返回
Nil
。它是闭包正文中的最后一条语句,因此总是返回
Nil
。(这是一个似乎应该修复的脚踏枪。请参阅中的进一步讨论)<
my $result = catch { ... }, { ... }, ...
# First non-throwing block short circuits evaluation:
say catch { 42 }                                     # 42
say catch { 42 }, { 99 }                             # 42
say catch { die }, { die }, { 99 }, { die }          # 99

# Returns a handled `Failure` if `catch` ends in an exception:
say catch { die 'foo' }, { die 'bar'}                # (HANDLED) bar

# (Or an UNhandled one, if that's preferred):
say .exception given catch { die 'baz'}, :!handle;   # baz

# Returns `Nil` if *and only if* a block does:
say catch { Nil }, { 42 }                            # Nil

# And still doesn't if told to *never* return a `Nil`:
say catch { Nil }, { 42 }, :!Nil;                    # 42
say catch { Nil }, { Nil }, :!Nil;                   # (HANDLED) Nil returned

# All blocks are passed the current exception:
say catch { die 'foo' }, { 42 if .payload eq 'foo' } # 42

# Including the first block:
$! = X::AdHoc.new: payload => 'First block gets initial `$!`';
say catch { when X::AdHoc { .payload } }             # First block gets initial `$!`
say catch { ... } // 42;                   # "(HANDLED) Stub code executed"
say catch { ... }, { 42 }                  # 42      <-- Do this instead.

# `$!` persists. Imagine code at end of prior section ran before code in this one:
say catch { when X::AdHoc { .payload } }   # First block gets initial `$!`

# If no payload is specified for `die` it picks up `$!`'s:
try { die }
say catch { when X::AdHoc { .payload } }   # First block gets initial `$!`

catch 22;           # "`catch` expects one or more blocks."
catch {}            # "Hash passed to `catch`. Use `{;}` instead of `{}`?"

# If you use a `when`, but it doesn't match, you don't get `Nil`:
say catch { when '(no match)' { .payload } }             # False
say catch { when '(no match)' { .payload }; default {} } # Nil (Verbose fix)
say catch { when '(no match)' { .payload }; Nil }        # Nil (Succinct fix)
multi catch ( *@blocks,           #= Assumes blocks, and only blocks, passed.
              :$handle = True,    #= If `Failure` returned, return it handled?
              :nil(:$Nil) = True  #= Is `Nil` to be considered an okay result?
            ) {

  # Grab caller's current lexical exception value
  $! = CLIENT::CALLER::CALLER::CALLER::<$!>; # No idea if this is reliable.
  
  my $result is default(Nil);       # `default(Nil)` to preserve `Nil` result

  for @blocks -> &block {
    $result = try { block $! }
    next if $result === Nil and !$Nil;
    return $result unless $!;       # Block didn't throw; return result
  }

  try { die 'Nil returned' } unless $! or $Nil;

  given Failure.new: $! {           # Last block threw exception; return `Failure`
    .handled = True if $handle;
    .return
  }
}
sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };
my $quotient = try { might-throw($a, $b) } // -1;
my $quotient is default(-1) = try { might-throw($a, $b) }
# Half decent validation and error messages, but, I presume, very slow:

subset valid-catch-blocks where {

  # Odd `Hash` / `Callable` conditions work around issues I don't understand.
  if .any | .all ~~ Hash { fail 'Hash passed to `catch`. Use `{;}` instead of `{}`?' }
  unless .any & .all ~~ Callable { fail '`catch` expects one or more blocks.' }

  for .kv -> $n, $_ {
    unless .count >= 1 and .arity <= 1 {
      die "`catch` blocks must accept one positional argument (current value of `$!`).\n"
        ~ "This signature is incompatible with that: {.gist}"
    }
  }

  True
}

# Unprofiled (premature optimizing) hacks to improve (?) dispatch performance.
# I think the `nextsame`s I've used end up calling the slow path anyway.
# I nevertheless feel it appropriate to include this code in this footnote:

our proto catch (|) is export {*}

multi catch ( *@blocks (&:($?))         where {True},             | ) { nextsame } # Fast?
multi catch ( *@blocks (&:($?), &:($?)) where {True},             | ) { nextsame }
multi catch ( *@blocks                  where valid-catch-blocks, | ) { nextsame } # Slow?
sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };
sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = sub { might-throw($a, $b); CATCH { default { return -1 } } }();
sub might-throw1($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do with try might-throw1($a, $b) { $_ } elsif $! { -1 };