generics vs. checked exceptions
John Rose
john.r.rose at oracle.com
Wed Mar 16 17:36:08 UTC 2022
Negora’s use case for demotion (temporary erasure, suppression) of
checked exceptions is streams. Streams are designed (quite nicely I
might add) on top of Java generics. But generics are (very consciously)
limited in their interactions with checked exceptions: There is no way
to treat exception *sets* as generic parameters.
You can treat individual exception types as generic parameters but this
doesn’t scale to lambda bodies which throw two checked exceptions, or
(less so) no checked exceptions. Exception tracking (including
“inferring” the throws of a lambda body) interoperates to some
extent with generic type inference, allowing single exception types to
be inferred and bound (constrained) to type variables, but the inference
rules don’t provide well other than a single exception.
In my mind this weakness of generics relative to checked exceptions is
similar to the weakness of generics relative to primitives. (Also the
weakness in not supporting variable-arity type abstraction; it’s hard
but doable.) And, in my mind, such weaknesses are ones we could fix
somehow, at some point when we have the bandwidth. But they are not
simple to fix: You don’t just propose a syntax and say “job
done”. The really hard part is specifying how the new feature
interacts with (pretty much) *every single pre-existing feature*. Oh,
and it has to be backward compatible. And unsurprising to users. And
fully specified with no gray areas. And more…
I note that both generics and exceptions are erased in translation to
classfiles; both depend on static checks in javac to maintain
correctness. This suggests (to me) that some sort of erasable,
statically checks inference of exception *sets* would be helpful. I
think Ron Pressler has mentioned this point as well somewhere.
The erasure in translation also unlocked a wide variety of retrofitting
moves when generics were introduced. Even at this late date if we were
to layer in a way to abstract over exception sets, we could hope (with a
little like) to find that we could retrofit streams to handle checked
exceptions. This would address Negora’s point (about streams) far
better than adding a new flavor of throws clause and/or lambda.
If we could stick a clause like “throws X” in various places, both
uses and defs of generic type variables, where X somehow was taken to
denote a possibly-empty set of checked exceptions, and then do the hard
work to specify the inference rules (with all the constraints mentioned
above) we might have a road towards making streams (and other APIs)
friendlier to checked exceptions. A stream would be retrofitted with
some sort of “throws X” variable which would advertise what the
terminal operations might throw, and various transforms would accumulate
exceptions. A new stream operation (non-terminal) for capturing and
transforming exceptions might be added; if : filter :: catch : <the new
thing>. (I didn’t say “monad” there but I could have.)
All that said, I don’t want to work on it now, because I have far more
pressing things to get done: pressing in a good, exciting, and fruitful
way. I imagine Brian has similar feelings.
(Idle question: Does fruitful pressing eventually lead to good
vintages? One hopes!)
— John
P.S. On one of my many back burners, I’ve been experimenting with
exposing checked exceptions through Java’s *existing* generics
(nothing new) to find the limits of what they can do. Here’s a
snapshot, FWIW, of small number of types which expose not only the
possible normal result value of an expression, but also its possible
exceptional result:
http://cr.openjdk.java.net/~jrose/draft/exbox-javadoc
It’s not done. But I find it thought-provoking and others might as
well.
More information about the amber-dev
mailing list