instanceof and exceptions

Tagir Valeev amaembo at gmail.com
Sat Oct 10 03:24:27 UTC 2020


Hello!

I don't like the idea of merging now, even though I advocated for it
before. First, it creates several declaration sites for the same variable.
It will complicate the navigation in IDE for users and for IDE authors as
well. We have a similar case when the same method is declared in two
interfaces and the user wants to navigate from the call-site to the method
declaration (in this case IDE goes to one of the declarations). But in that
case, there are actually two super-methods. And here we have one variable
with several declarations. Second, it could be really hard to understand
the type of the variable because it could be scattered over many
expressions. Moreover, this may cause accidental reuse of the same name.
And this opens a way for context-dependent types. E.g. consider the
statement:
if((obj instanceof A x && testA(x)) || (obj instanceof B x && testB(x)))
useAorB(x);
Here the type if `x` will depend on the place where it's used. I think we
don't like to go to this direction.

That said, I like OR patterns. I think having a LUB type for the pattern
variable is completely ok for now. It's consistent with catch and useful
enough. In IntelliJ IDEA we have quite many code patterns that can benefit
from this. E.g. (just did quick structural search through the codebase):

final PsiElement element = descriptor.getPsiElement();
if (element instanceof PsiNewExpression || element instanceof
PsiArrayInitializerExpression) {
  PsiReplacementUtil.replaceExpression((PsiExpression)element,
myEnumName + ".values()");
}

Could be replaced with

if (descriptor.getPsiElement() instanceof (PsiNewExpression |
PsiArrayInitializerExpression) expression) {
  PsiReplacementUtil.replaceExpression(expression, myEnumName + ".values()");
}

Or

Object normalized = value;
if (value instanceof Byte || value instanceof Short) {
  normalized = ((Number)value).intValue();
}

Could be replaced with

if (value instanceof (Byte | Short) number) {
  normalized = number.intValue();
}

And so on. This allows reducing the number of typecasts even further.

With best regards,
Tagir Valeev.


пт, 9 окт. 2020 г., 21:59 Brian Goetz <brian.goetz at oracle.com>:

> There's a simple (though unsatisfying) answer to your student's question:
> because catch is special.  (But this answer belies an important point:
> ad-hoc syntactic "patches" like multi-catch may be satisfying in the short
> term, but they always beget a flurry of "for consistency" arguments, which
> can often not be consistently satisfied.)
>
> Essentially, you are asking whether (a) OR patterns make sense, and (b)
> whether we can reuse the syntax A|B for OR'ed type patterns.  Let's pull on
> that string for a bit.
>
> Suppose that `(T|U) tu` were a type pattern.  The type test is clear
> enough, but what is the type of `tu`?  There are two candidates; a union
> type and LUB.  (Union types are clearly more general, but what does
> multi-catch do?  It does LUB.  See JLS 14.20.)  I suspect your preferred
> answer is "union type", since that's a more information-preserving answer.
> One downside here is it provides a(nother) vector for non-denotable weird
> types to escape to where users can observe them.  (We faced a similar issue
> in Lambda (and LVTI): both allowed intersection types to escape more freely
> into the wild, and both had consequences.)  It's a possibility, but I'm not
> sure it clears the bar for risk/reward.
>
> A more general answer that doesn't involve introducing new syntactic forms
> into the language would be to dust off the proposal for binding variable
> merging:
>
>     if (x instanceof RuntimeException e || x instanceof Error e) { X }
>
> Our original design allowed for this but we backed off because it was
> unclear whether the return-on-complexity was justified.  Here, we'd see
> that we have two patterns, each with a binding of the same name, and
> exactly one of which can produce a binding at X.  So we can give `e` either
> a LUB or union type, just as with the "shortcut" T|U, but its a more
> general solution, and doesn't tease users with a "fake" union type syntax.
>
> Note that the "merging" situation has another analogue with the same
> semantics: fallthrough.  The above is like:
>
>     switch (x) {
>         case RuntimeException e:
>         case Error e:
>             X;
>     }
>
> If fake union types are not great, what about real union types?  Well,
> it's been shown to be possible (
> https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.158.1253&rep=rep1&type=pdf),
> but its a lot; it seems like the cost-benefit isn't there either.
>
> Overall, I'm skeptical that the cost/benefit of union type patterns is
> positive, but they are doable.
>
>
>
>
>
> On 10/9/2020 6:11 AM, Remi Forax wrote:
>
> Following the course on exceptions, where i explain that a catch() is an instanceof,
> two different students ask me why catch() can use '|' in between the exception types but instanceof can not.
>
> i.e why this code works
>   try {
>     ...
>   } catch(RuntimeException | Error e) {
>     throw e;
>   } catch(Throwable t) {
>     ...
>   }
>
> but this one doesn't
>   try {
>     ...
>   } catch(Throwable t) {
>     if (t instanceof RuntimeException | Error e) {
>       throw e;
>     }
>     ...
>   }
>
> I wonder if people will want to do pattern matching on exceptions ?
>
> Rémi
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20201010/660cccca/attachment-0001.htm>


More information about the amber-spec-experts mailing list