Fwd: switch-case-catch
Brian Goetz
brian.goetz at oracle.com
Tue Jul 20 13:58:26 UTC 2021
After a few minutes more thought, this approach can be simulated pretty
well by a library solution.
Suppose you wrote the following class (once):
sealed class Result<T, E extends Exception> {
record Succ<T, E extends Exception>(T t) extends Result<T,E> { }
record Fail<T, E extends Exception>(E e) extends Result<T,E> { }
interface ExceptionalSupplier<T, E extends Exception> {
T get() throws E;
}
static<T, E extends Exception> Result<T,E>
of(ExceptionalSupplier<T,E> s) {
try {
return new Succ(s.get());
}
catch (Exception e) {
return new Fail(e);
}
}
}
Note that no library needs to directly return Result; you can wrap an
exceptional computation with Result.of(() -> expr).
Now, you can switch using ordinary pattern matching:
switch (Result.of(() -> possiblyExceptionalExpression()) {
case Succ(T t) -> ...
case Fail(E e) -> ...
}
And note that nested patterns work just fine here:
switch (Result.of(() -> possiblyExceptionalExpression()) {
case Succ(T t) -> ...
case Fail(FooException e) -> ...
case Fail(BarException e) -> ...
}
(Critics will say "but, that's more ceremony", because you have to wrap
with a Result and then distinguish between Succ and Fail. Supporters
would say that this is a small price for saying what you mean.)
On 7/20/2021 9:15 AM, Brian Goetz wrote:
> The following idea was received on amber-spec-comments.
>
> Essentially, the OP is asking that, if we're extending switch to cover
> more different kinds of cases, why not further extend it to treat an
> exception in evaluating the target as another kind of matchable
> result. It is a little like the current treatment of null, where, if
> there is a `null` case, it is treated as a switchable value, and if
> not, the switch completes abruptly with NPE.
>
> While the questions are fair questions to ask, I see several
> challenges with this approach:
>
> - The motivations are mostly syntactic; `case X -> S` is more compact
> than `catch (E e) { S }`. If these two had similar syntactic weight,
> it is less likely such suggestions would be made.
>
> - It only reduces the syntactic weight of try-catch in switches, but
> not other constructs. This will surely be a "tomorrow's problems come
> from today's 'solutions'" thing.
>
> - People were uncomfortable about the "action at a distance" problem
> with other aspects of switch; this creates new ones (you have to scan
> all the cases to see which exceptions in evaluating the target are
> handled.)
>
> - The `catch` case labels are not being matched against the target of
> the switch, making the basic model for the body of a switch more
> complicated.
>
> - There are two possible interpretations of the approach; the
> desugaring suggested in the note seems the less sensible of the two to
> me. They are:
>
> - The switch target is evaluated. If evaluation completes
> normally, the non-catch cases are tried, otherwise the switch
> completes abruptly. If evaluation completes exceptionally, the catch
> cases are tried, and if none of those match, the switch completes
> abruptly. In this version, only exceptions thrown by evaluation of
> the target can match.
>
> - The entire switch is considered to be wrapped by a big try-catch
> (this is what the OP suggests.) This means exceptions thrown from the
> body of a previous case can also be caught by a later catch case.
>
> If the motivation is to be more like a traditional Result<T,E>, the
> former interpretation makes more sense, because it more directly
> models switching over a Result<T,E>, with sugar to deconstruct the
> Result wrapper.
>
>
> -------- Forwarded Message --------
> Subject: switch-case-catch
> Date: Tue, 20 Jul 2021 09:01:14 +0300
> From: Omar Aloraini <aloraini.omar at gmail.com>
> To: amber-spec-comments at openjdk.java.net
>
>
>
> Hello,
>
>
> With the introduction of sealed hierarchies and the enhancement of switch
> expressions, I find myself wondering about the way I model errors.
> Previously using a class `Result<T>` to model success or failure of an
> operation was something that I used to do, but I quickly reverted as
> all my
> code base methods returned the `Result` type, sometime I introduce another
> type to model the error type such as Result<T,E>, where E is typically a
> subtype of Exception. But again things get cumbersome, fortunately we now
> have sealed hierarchies, which allows us to model multiple possible
> outcomes of an operation, or a closed set of error conditions. Client code
> is not ugly anymore as switch expressions and pattern matching amend it to
> become more concise and pleasant to read and write, or as someone whom I
> respected would say ‘less ceremony’. Now I find myself wondering why not
> use Exceptions? For some method A that returns a value of type *T* or
> throws an exception of two possible types, something like `T A() throws
> E1,E2`, obviously you will lose that pleasant client code with switch and
> pattern matching. But what if we had switch-case-catch where the catch
> clauses must cover all exceptions in the throws clause for it to compile.
>
> Something like the following:
>
> Object r = switch (o.A()){
>
> case T t -> ...
>
> catch E1 e -> ...
>
> catch E1 e -> ...
>
> }
>
> Semantically it would equivalent to:
>
> Object r;
>
> try {
>
> r = switch (o.A()) {
>
> case T t -> ...
>
> } catch (E1 e) {
>
> r = ...
>
> } catch (E2 e) {
>
> r = ...
>
> }
>
> }
>
> You will also get the benefit of pattern matching for the catch clause,
> something like `catch E1 | E2`.
>
> I don't think it's necessary to use the `catch` keyword, `case` might just
> work.
>
> Apologies if this was suggested before.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20210720/cb9a68a1/attachment.htm>
More information about the amber-spec-experts
mailing list