# See https://stackoverflow.com/questions/51644197
use lib '.';
# `trys` in detail
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
# value(s) you want rejected if they smartmatch:
say trys :reject(Nil,/o/), {Nil}, {'no'}, {2} # 2
# If all callables throw, return `Failure` wrapping exceptions(s):
say trys :reject(Nil), {Nil} # (HANDLED) Rejected Nil
say trys {die} # (HANDLED) Died
say trys {(42/0).Str} # (HANDLED) Attempt to divide by zero
# Specify `:!HANDLED` if the returned `Failure` is to be left unhandled:
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
# Topic of subsequent callables is exception from prior failed callable:
trys {die 'bar'}, *.say; # bar
trys {fail 'bar'}, {die "$_ baz"}, *.say; # bar baz
# Caller's `$!` is left alone (presuming no `trys` bug):
say $!; # foo
# To include *all* throws in `Failure`, specify `:all-throws`:
say trys {die 1}, {die 2}, :all-throws; # (HANDLED) foo 1 2
# Note the `foo` -- `all-throws` includes the caller's original `$!`.
```
# `trys` "traps"
```
# Some "traps" are specific to the way `trys` works:
say trys { ... } // 42; # "(HANDLED) Stub code executed"
say trys { ... }, { 42 } # 42 <-- List of blocks, no `//`.
#trys 22; # Type check failed ... got Int (22)
say trys { 22 } # 22 <-- Block, not statement.
#trys {} # Type check failed ... got Hash ({})
say trys {;} # Nil <-- Block, not Hash.
# Other "traps" are due to the way Raku works:
# WAT `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) <-- Builtin `try` clears `$!`.
say $!; # (Any) <-- Caller's too!
$! = X::AdHoc.new: payload => 'foo';
say trys {$!} # (Any) <-- `trys` clears `$!` BUT:
say $!; # foo <-- Caller's `$!` left alone.
$! = X::AdHoc.new: payload => 'foo';
say try {$!.self} # foo <-- A fix with builtin `try`.
say $!; # (Any) <-- Caller's `$!` still gone.
$! = X::AdHoc.new: payload => 'foo';
say trys {.self} # foo <-- Similar fix with `trys`.
say $!; # foo <-- Caller's `$!` left alone.
# See https://stackoverflow.com/questions/51644197/
unit module X2;
our sub trys ( **@callables, #= List of callables.
:$reject = (), #= Value(s) to be rejected.
:$all-throws = False, #= Return *all* thrown exceptions?
:$HANDLED = True, #= Mark returned `Failure` handled?
) is export {
my @throws; #= For storing all throws if `$all-throws`.
$! = CLIENT::<$!>; # First callable's `$!` is `trys` caller's.
@throws.push: $! if $! && $all-throws; # Include caller's `$!` in list of throws.
my $result is default(Nil); # At least temporarily preserve a `Nil` result.
for @callables -> &callable {
$result = try { callable $! } # `try` next callable, passing `$!` from prior callable as topic.
if not $! and $result ~~ $reject.any # Promote result to exception?
{ $! = X::AdHoc.new: payload => "Rejected $result.gist()" }
@throws.push: $! if $! && $all-throws;
return $result if not $!; # Return result if callable didn't throw.
}
$! = X::AdHoc.new: payload => @throws if $all-throws;
given Failure.new: $! { # Convert exception(s) to `Failure`.
.handled = $HANDLED;
.return
}
}