Language feature to improve checked exceptions
Brian Goetz
brian.goetz at oracle.com
Tue Mar 7 14:56:44 UTC 2023
Thanks for the link to Koka.
Indeed, I spent many hours prototyping something exactly like this for
Streams back in the Java 8 days, as an extension to one of the
"exception transparency" proposals that came up at the time. As you
point out, in libraries like streams, a throwing lambda passed as a
parameter "pollutes" the type of the returned stream, since the lambda
is incorporated into the stream and may be invoked by a later operation,
and therefore might be thrown from that later operation.
This basically asks the generic type system to have type variables to
track effects as well as types (see, e.g., "Type and Effect Systems",
Nielsen and Nielsen.)
As your example shows, it is possible to do this with (a) some form of
variadic generics and (b) some algebraic operations on variadic
parameters (e.g., concatenating, unioning, differencing). It also means
that generic classes like Stream have to carry around additional type
variables describing their exceptions (effects), and those tvars are
"different" from ordinary tvars.
Our conclusion at the time we did this experiment is that, while the
approach is viable, annotation burden on library authors is high, and
this burden flows through to clients as well. (Even if we allow use
sites to elide the exception information, so they can say Stream<String>
rather than Stream<String, ExceptionGoop>, it still flows into the docs
and error messages.) It also makes generics "even more complex" (and
people still complain generics are too complex.)
So the outcome of this experiment was "yes, it can be made to work, but
no, we don't think putting into Java at this time is likely to be seen
entirely positively."
> While I do believe that the actual solution will require much more
> thought than this (e.g. deconstruction of generic sums to be able to
> handle specific exceptions and remove those exceptions from the final
> union type of exceptions), this is a showcase that a nice and
> composable checked exceptions works even without Monadic types.
>
Indeed. The need for asymmetric difference as one of the algebraic
operations on effect variables is one of those surprises that bites you
the first time you encounter it.
> I find this direction much nicer than using Either everywhere.
>
I am not sure whether I do or not, which is one reason we've not done
either yet...
On 3/7/2023 8:24 AM, Holo The Sage Wolf wrote:
>
> This is a continuation of this thread
> <https://mail.openjdk.org/pipermail/amber-dev/2023-March/007849.html>,
> unfortunately I only joined the mailing list, so I can’t reply to it
> so I apologize for opening a new thread.
>
> I want to point to Koka
> <https://koka-lang.github.io/koka/doc/book.html#why-effects>, Koka is
> a language built upon the concept of Effect, a generalization of how
> Java treats checked exceptions (and similarly to what Brian Goetz said
> about how Checked exceptions are equivalent to Either, general Effect
> system is equivalent to full monadic type system).
>
> The language is very interesting and worth reading about, but the
> relevant part is their Polymorphic Effects, which allow you to extend
> the effects. In Java pseudo code, it means that the following is valid:
>
> |@FunctionalInterface public interface EPredicate<T, E extends
> Throwable> { boolean test(T t) throws E; } @FunctionalInterface public
> interface EConsumer<T, E extends Throwable> { boolean test(T t) throws
> E; } public class EStream<V,E extends Throwable> { public <EX extends
> Throwable> EStream<V, (E|EX)> filter(EPredicate<? super T, (E|EX)>
> predicate) { ... } public <EX extends Throwable> void
> forEach(EConsumer<? super T, (E|EX)> action) throws E, EX { ... } } |
>
> Which allows you to have a fluent composition without losing the
> fine-grained checked exceptions:
>
> |public static void main(string[] args) throws FileNotFoundException,
> IllegalAccessException, SQLException { myEStream.filter(v -> ...) //
> something that throws FileNotFoundException .filter(v -> ...) //
> something that throws IllegalAccessException .forEach(v -> ...); //
> something that throws SQLException } |
>
> This also makes the type system of the |throws| clause a subset of the
> generics type system.
>
> While I do believe that the actual solution will require much more
> thought than this (e.g. deconstruction of generic sums to be able to
> handle specific exceptions and remove those exceptions from the final
> union type of exceptions), this is a showcase that a nice and
> composable checked exceptions works even without Monadic types.
>
> I find this direction much nicer than using Either everywhere.
>
> --
> Holo The Wise Wolf Of Yoitsu
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230307/d934f3cb/attachment.htm>
More information about the amber-dev
mailing list