Add Throwables.sneakyThrow

Issue #30 resolved
John Kozlov created an issue

Sometimes it is necessary to throw a checked exception, but the enclosing method has no throws keyword. Example:

FluentIterable.from(Collections.list(NetworkInterface.getNetworkInterfaces())).filter(
    new Predicate<NetworkInterface>() {
         @Override public boolean apply(NetworkInterface iface) {
             return !iface.isLoopback();
          }
    });

Since Predicate.apply has no declared Exception that can be thrown, this code does not compile. I could use Throwables.propagate, but I don't want to wrap SocketException into RuntimeException.

This is a severe issue because I cannot use filter() anymore and instead I have to use an imperative loop.

The solution is to add a method that can sneaky throw any Throwable even if it is checked:

public static RuntimeException sneakyThrow(Throwable t) {
    throw Throwables.<RuntimeException> sneakyThrowInner(t);
}

private static <T extends Throwable> T sneakyThrowInner(Throwable t) throws T {
    throw (T) t;
}

Then I can handle a SocketException using sneakyThrow:

try {
    return !iface.isLoopback();
} catch (SocketException ex) {
    throw sneakyThrow(ex);
}

Also, see the original discussion in Guava issues: https://github.com/google/guava/issues/626

Comments (20)

  1. Anund McKague

    Hi @orionll,

    Can you expand on how you want to treat the exception case differently than just straight filtering? A valid solution here would be to just return false if an exception were thrown. You probably don't want to do that so please expand a little on you example.

    Alternatively this problem can also be solved by using the Eithers#sequence function.

    Either<SocketException, List<NetworkInterface>> networkInterfaces;
    try {
      networkInterfaces = right(Collections.list(NetworkInterface.getNetworkInterfaces()));
    } catch (SocketException s) {
      networkInterfaces = left(s);
    }
    Either<SocketException, Iterable<NetworkInterface>> sequenced = networkInterfaces.flatMap(ifaces -> {
      Iterable<Either<SocketException, NetworkInterface>> mapped = Iterables.collect(ifaces, iface -> {
        try {
          if (iface.isLoopback()) {
            return some(right(iface));
          } else {
            return none();
          }
        } catch (SocketException s) {
          return some(left(s));
        }
      });
      return Eithers.sequenceRight(mapped);
    });
    
    // if sequenced is a Right all of the network interfaces returned a result from isLoopback
    // if sequenced is a Left it's the first exception that was thrown.
    

    You can simplify the boiler plate of the first try block and maybe the second. Note that this solution obscures the point where the SocketExcpetion was thrown. A more complete solution would use an error type on the left to distinguish where exactly the exception was generated.

  2. Jed Wesley-Smith

    fugue is dedicated to providing alternatives to side-effects, and in particular a useful Either that can represent the disjunction of an error or a result.

    This is not something we would want to encourage although we should provide more help for sensible alternatives such as what @amckague is proposing.

  3. Jed Wesley-Smith

    fugue is dedicated to providing alternatives to side-effects, and in particular a useful Either that can represent the disjunction of an error or a result.

    This is not something we would want to encourage although we should provide more help for sensible alternatives such as what @amckague is proposing.

  4. Anund McKague

    @orionll there's room for something like:

     <A, E extends Exception> Eithers.catching(Suppiler<A>): Either<E,A>
     // and/or
     <A, B, E extends Exception> Eithers.catching(Suppiler<A>, Function<E,B>): Either<B,A>
    

    to help reduce the number of times you'd need to try/catch things. Is there other work that could be done from this issue?

  5. John Kozlov reporter

    Using Either is not always possible. For example, what to do if I want to throw SQLException inside Effect.apply?

  6. Jed Wesley-Smith

    If you want to throw exceptions, and then handle the result, then Effect isn't the thing you want. You should use map and return Either<SQLException, A>. You can then rethrow that SQLException from a method that declares is if you really want to (not a recommendation).

  7. John Kozlov reporter

    What if I have no map? My structure is not a functor. It is not generic at all: DoubleArray implements Applicant<Double>. How would you rewrite this code with Either?

    DoubleArray array = ...
    createSQLTable();
    array.foreach(d -> fillSQLTable(d));
    
  8. Jed Wesley-Smith

    Then I am missing the justification for why this should be added to fugue, and not to some other library. This is quite orthogonal to the objectives of this library, which is to provide useful foundations for functional programming in Java.

  9. Jed Wesley-Smith

    Very good question. The actual reason it was added is lost to me now, I certainly remember not particularly liking it, but there being a strong internal campaign for it. I acquiesced, but still see it as a wart in the library.

    IIRC it was something about making the interface between pure code and imperative code easier, and was pitched as a convenience that would help increase the rate of adoption of functional code. I am skeptical that it really achieves this result however.

    edit it is hard to get rid of code once introduced. I'd love to remove Throwables and note that it is deprecated in fuge 3.0.

  10. Jean-Baptiste Giraudeau

    Throwables is deprecated and has been removed from fugue 3... I also use sneaky throw for one or two use case in our code base - to please a framework I had to use - but I would rather have it use not be widespread; it does break java type system: you cannot catch a checked exception that have been sneaky thrown. (without resorting to a stub method that threats to throw that exception without actually doing anything)

  11. Anund McKague

    We can agree the intersection of functional interfaces and checked exceptions is not pleasant. We can also agree that you are not always able to express your program in terms of an Either. You are correct this solves the problem presented by checked exceptions without wrapping and rethrowing. It is more succinct. It does not avoid the need to cast the thrown exception back to a useful type (for checked exceptions). The move to deprecate Throwables means sneakyThrow no longer fits in this library.

    Where you find it necessary for sneakyThrow it is a small enough snippet of code to just copy it around. A memorable tool that can be applied at need.

  12. Anund McKague

    It's been moved to: https://maven.atlassian.com/repository/internal/com/atlassian/fugue/fugue-guava/3.0.0-m006/fugue-guava-3.0.0-m006.jar Quick warning, it's missing an osgi manifest and the code you're looking for has moved packages to avoid osgi split packages problems. Have a read of the most recent 3.0 change log https://bitbucket.org/atlassian/fugue/src/9877c33c483bd4769b6b3dee3e73f9a3cacbf49f/changelog.md?at=v3.0.x

    The move to remove it in 3.0 would probably come too soon given that the deprecation was introduced and released at roughly the same point in time.

    At the end of this there will be an artefact in maven central that easier to find things for. The internal proxy is making this harder than it needs to be.

  13. Log in to comment