TL;DR此答案的最后一部分讨论了您尝试中发生的其他人未能解决的问题[1 2]。其余部分介绍trys,这是一个全面解决您的 Q 所展示的整体问题的解决方案。
trys总结
几个非常简单的例子:
say trys { die }, { -1 } # -1
say trys { die }, { when X::AdHoc { 42 } } # 42
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
}
}
Code on 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.)
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 `$!`.)
trys 与内置插件 try 和 CATCH
trys 结合了try 和CATCH 的选定部分:
每个trys callable 可以扮演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 } } };
CATCH 块始终返回 Nil。它是闭包主体中的最后一条语句,因此始终返回 Nil。 (这是一把似乎应该修理的足枪。请参阅Actually CATCHing exceptions without creating GOTO 中的进一步讨论)
我可能会写例如:
my $quotient = try { might-throw($a, $b) } // -1;
我正在评估的表达式可能确实有一个未定义的值,我无法将这与引发异常的情况区分开来。
你可以改为:
my $quotient is default(-1) = try { might-throw($a, $b) }
这里发生了什么:
如果想要区分由于抛出异常而返回的Nil 和由于Nil 的普通返回,这可能仍然不能令人满意。或者,也许更重要的是:
我可能想根据抛出的异常的类别回退到不同的值,但 try 只是将它们全部吞下。
这需要解决方案,但不是CATCH:
我可以将自己的 CATCH 块放在 try 中以区分异常,但我又回到上面的第一个案例
相反,现在我创建了 trys 函数。
脚注
[1] 正如您所指出的:“当前的答案......过于狭隘地关注除法运算符的语义。”。所以我在脚注下对这方面的总结进行了注释:为了支持advanced math,Raku 不会自动将理性除以零(例如1/0)视为异常/错误。 Raku 随之而来的双重延迟异常处理是一个红鲱鱼。
[2]CATCH 也是红鲱鱼。即使与.resume 一起使用,它也不会返回值或注入值,因此它是完成需要完成的工作的错误工具。
[3] 有些人可能认为trys 最好拼写为tries。但我故意拼写为trys。为什么?因为:
-
在英语中,就tries 这个词与try 的关系而言,它非常 密切相关。单词选择trys 的奇怪之处在于提醒人们它不仅仅是复数try。也就是说,粗略的含义与try 密切相关有点,所以拼写为trys 在imo 中仍然有意义。
-
我喜欢奇思妙想。显然,在阿尔巴尼亚语中,trys 的意思是“按压、压缩、挤压”。与try 一样,trys 函数“按下”代码(“按下”在“压力”意义上),并“压缩”它(与不使用 trys 的冗长相比),以及“将所有与异常相关的错误机制——Exceptions、Failures、Nils、try、CATCH、.resume——合二为一。
-
在立陶宛语中,trys 的意思是“三”。 trys:
-
拒绝三种结果:Exceptions; Failures;和用户指定的:reject 值。
-
以三种方式保持滚动:将调用者的$! 传递给第一个可调用对象;调用带有最后一个异常的后续可调用对象作为他们的主题;将最后一个块中抛出的异常转换为Failure。
-
解决编程中最难的事情之一——命名:trys 与try 明显相似但不同;我在此预测很少有开发者会在他们的代码中使用阿尔巴尼亚语或立陶宛语trys;选择trys 而不是tries 可以减少与现有代码发生冲突的可能性。 :)