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的异常处理可以像我所表达的那样,我希望它能够在上面做吗?
编辑:
当前的答案是提供信息的,但是过于狭隘地关注除法运算符的语义.我稍微改写了这个问题,使异常的主要问题更加重要.
你的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
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 内置try和CATCHtrys结合了选定的部件try和CATCH:
就像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意思是“按压、压缩、挤压”。就像try,trys函数“按下”代码(“压力”意义上的“按下”),并“压缩”它(与不使用的冗长相比trys),并“挤压”所有与异常相关的错误机制 - - Exceptions, Failures, Nils, try, CATCH, .resume-- 合二为一。
在立陶宛语中,trys意思是“三个”。trys:
拒绝三种结果:Exceptions;Failures; 和用户指定的:reject值。
以三种方式使事情滚动:将调用者的传递$!给第一个可调用对象;以最后一个异常为主题调用后续可调用对象;将最后一个块中抛出的异常转换为Failure.
解决了编程中最难的事情之一——命名事物:trys与try明显的方式相似但又不同;我在此预测很少有开发人员会trys在他们的代码中使用阿尔巴尼亚语或立陶宛语;选择trys而不是tries使其不太可能与现有代码发生冲突。:)
我有以下工作:
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)