Skip to content

Commit

Permalink
Elaboration of Failures
Browse files Browse the repository at this point in the history
Explain use fatal in place under Failure rather than just in pragmas,
and explain the `with` handling idiom.

The idiom is useful in explaining why one might use topicalization
with `else`, so also include that.

Finally, fix the with/orwith/without heading; unlike the other control
structures, it's lacking code tagging in the header.
  • Loading branch information
treyharris committed Jun 26, 2019
1 parent 30516ac commit 1c57f0d
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 6 deletions.
45 changes: 42 additions & 3 deletions doc/Language/control.pod6
Expand Up @@ -357,10 +357,10 @@ unless 0 { "0 is false".say } ; # says "0 is false"
my $c = 0; say (1, (unless 0 { $c += 42; 2; }), 3, $c); # says "(1 2 3 42)"
my $d = 0; say (1, (unless 1 { $d += 42; 2; }), 3, $d); # says "(1 3 0)"
=head2 X<with|control flow, with>, X<orwith|control flow, orwith>, X<without|control flow, without>
=head2 X<C<with>|control flow, with>, X<C<orwith>|control flow, orwith>, X<C<without>|control flow, without>
The C<with> statement is like C<if> but tests for definedness rather than
truth. In addition, it topicalizes on the condition, much like C<given>:
The C<with> statement is like C<if>, but tests for definedness rather than
truth, and it topicalizes on the condition, much like C<given>:
with "abc".index("a") { .say } # prints 0
Expand Down Expand Up @@ -390,6 +390,45 @@ There are also C<with> and C<without> statement modifiers:
say 42 with $answer;
warn "undefined answer" without $answer;
As with the other chainable constructs, an C<else> completing a
C<with/if>..C<orwith/else> chain will itself topicalize to the value
of the prior (failed) condition's topic (either the topic of C<with>
or the final C<orwith> or C<elsif>).
In the case of an C<else> following a C<with> or C<orwith>,
topicalizing a value guaranteed to be undefined may seem useless. But
it makes for a useful idiom when used in conjunction with operations
that may fail, because L<Failure|/type/Failure> values are always
undefined:
=begin code
sub may_fail( --> Numeric:D ) {
my $value = (^10).pick || fail "Zero is unacceptable";
fail "Odd is also not okay" if $value % 2;
return $value;
}
with may_fail() -> $value { # defined, so didn't fail
say "I know $value isn't zero or odd."
} else { # undefined, so failed, and the Failure is the topic
say "Uh-oh: {.exception.message}."
}
=end code
Note that while topicalizing a L<Failure|/type/Failure> marks it
L<C<handled>|/type/Failure#method_handled>—so you can use the
C<with>/C<else> to proceed safely with execution—it doesn't make the
I<Failure value itself> safe. Even within the C<else> clause, if you
try to use the value directly, it will result in your C<else> clause
itself failing (or, in Rakudo, "promoting" the Failure into a thrown
exception).
But as seen above, you I<can> use the methods of a handled Failure
object the C<else> topicalizes, such as
L<C<exception>|/type/Failure#method_exception>, if you wish to provide
diagnostics or interrogate the underlying
L<Exception|/type/Exception>.
=head1 X<when|control flow, when>
The C<when> block is similar to an C<if> block and either or both can be used in
Expand Down
37 changes: 34 additions & 3 deletions doc/Type/Failure.pod6
Expand Up @@ -6,11 +6,24 @@
class Failure is Nil { }
A C<Failure> is a I<soft> or I<unthrown> exception, usually generated by
calling C<&fail>. It acts as a wrapper around an L<Exception|/type/Exception> object.
A C<Failure> is a I<soft> or I<unthrown> L<Exception|/type/Exception>,
usually generated by calling C<&fail>. It acts as a wrapper around an
L<Exception|/type/Exception> object.
Sink (void) context causes a Failure to throw, i.e. turn into a normal
exception.
exception. The L<C<use fatal> pragma|/language/pragmas#index-entry-fatal-fatal>
causes this to happen in all contexts within the pragma's
scope. Inside L<C<try>
blocks|/language/exceptions#index-entry-try_blocks-try>, C<use fatal>
is automatically set, and you can I<disable> it with C<no fatal>.
That means that Failures are generally only useful in cases of
code that normally would produce an rvalue; Failures are more or less
equivalent to Exceptions in code that will frequently be called in sink
context (i.e., for its side-effects, such as with C<say>).
Similarly, you should generally use C<&fail> only inside code that is
normally expected to return something.
Checking a Failure for truth (with the C<Bool> method) or definedness (with
the C<defined> method) marks the failure as handled, and causes it not to
Expand All @@ -22,6 +35,24 @@ Calling methods on unhandled failures propagates the failure. The
specification says the result is another C<Failure>, in Rakudo it causes the
failure to throw.
Because a Failure is L<C<Nil>|/type/Nil>, which is undefined, a common idiom
for safely executing code that may fail uses a
L<C<with/else>|/language/control#with,_orwith,_without> statement:
=begin code
sub may_fail( --> Numeric:D ) {
my $value = (^10).pick || fail "Zero is unacceptable";
fail "Odd is also not okay" if $value % 2;
return $value;
}
with may_fail() -> $value { # defined, so didn't fail
say "I know $value isn't zero or odd."
} else { # undefined, so failed, and the Failure is the topic
say "Uh-oh: {.exception.message}."
}
=end code
=head1 Methods
=head2 method new
Expand Down

0 comments on commit 1c57f0d

Please sign in to comment.