google / guava

Google core libraries for Java
Apache License 2.0
50.03k stars 10.86k forks source link

Support for something like a ThrowingRunnable #2828

Open Bocete opened 7 years ago

Bocete commented 7 years ago

Runnable doesn't throw exceptions. Callable throws all Exceptions and has a return value, forcing one to declare a Callable argument in their code, and forcing users of the API to "return null" at the end of their lambdas for no good reason.

A ThrowingRunnable that throws a generic exception type would be really handy. Something like:

public interface ThrowingRunnable<T extends Exception> {
    public void run() throws T;
}

I don't know how many times I defined a ThrowingRunnable that throws a specific exception type. This seems pretty useful with Java 8 for helper utilities that take a lambda that throws something and throws that same thing in return. Here's a few ideas:

/** Runs the runnable if the executionIndentifier hasn't been seen before */
public class ExecutionMemoizer {
    public <T extends Exception> void runOnce(ThrowingRunnable<T> throwingRunnable, Object executionIdentifier) throws T {...}
}

public class ThrowingRunnables {
    public static <T extends Exception> ThrowingRunnable<T> sequence(ThrowingRunnable<? extends T>... runnables) {...}
}

My first question should be, I guess, why doesn't this exist already. It's not a far fetched idea, somebody probably thought of this before. There must be a reason, and I'm curious about it.

This maybe seen as a step to reimplementing java.util.function to support throwing exceptions. I'm down with that too. It seems like the norm today is, when using java.util.function stuff with lambdas that throw exceptions, to wrap exceptions in runtime exceptions and unpack at the other side. That's a bad idea and nobody should be doing that. There should be a working alternative and Guava is the place to provide it.

If you think this would be a nice thing to have, I'll contribute.

Thanks!

ben-manes commented 7 years ago

This is available as Checked types in other utilities, e.g. jOOL and vavr. Its probably better to add one of them instead.

kashike commented 7 years ago

Available in MµG as well.

lowasser commented 7 years ago

So, we have been thinking about this, but the approach we've been using, at least within Google, is a little surprising.

We tended to see reinventing java.util.function or especially java.util.stream as a non-starter. Additionally, the situation gets astronomically more complex if you have multiple checked exception types; you either need functional types for 2, 3, etc. checked exceptions, or you need to accept a common supertype of all your checked exceptions, which isn't really satisfactory.

So what we did instead was to introduce a class TunnelException. It looks like an entirely conventional runtime exception that wraps a checked exception. It doesn't expose a constructor; there's a static method

<T> T tunnel(Callable<T> lambda)

...which runs lambda and wraps any thrown exceptions in a TunnelException. So far this isn't much different from just wrapping your checked exceptions in a RuntimeException. It's just a specific type of runtime exception.

But what we did on top of that was add specialized support in Error Prone that enforces at compile time that you catch TunnelException in the same method, and "un-tunnel" it: call TunnelException.getCauseAs(SpecificExceptionType.class) to extract exactly the checked exception types that the lambda could throw. From there, you can rethrow them outside the method, handle them directly, or whatever you choose, just as a normal exception. So your checked exceptions go through a RuntimeException "tunnel," and the compiler makes sure the tunnel comes out somewhere.

We haven't open-sourced this yet because it's in a somewhat weird position: it's a library that is usable anywhere, but its design is built around the expectation of compiler support from Error Prone to make sure you got the checked exceptions right. Without Error Prone, it's just wrapping checked exceptions in a runtime exception that's a little dressed up.