Exception 从Perl 6中的异常处理程序返回值
我一直在尝试编写一个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
-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 };