Giter VIP home page Giter VIP logo

faux-pas's Introduction

Faux Pas: Error handling in Functional Programming

Spilled coffee

Stability: Sustained Build Status Coverage Status Code Quality Javadoc Release Maven Central License

Faux pas noun, /fəʊ pɑː/: blunder; misstep, false step

Faux Pas is a library that simplifies error handling for Functional Programming in Java. It fixes the issue that none of the functional interfaces in the Java Runtime by default is allowed to throw checked exceptions.

  • Technology stack: Java 8+, functional interfaces
  • Status: 0.x, originally ported from Riptide, used in production

Example

interface Client {
    User read(final String name) throws IOException;
}

Function<String, User> readUser = throwingFunction(client::read);
readUser.apply("Bob"); // may throw IOException directly

Features

  • Checked exceptions for functional interfaces
  • Compatible with the JDK types

Dependencies

  • Java 8 or higher
  • Lombok (no runtime dependency)

Installation

Add the following dependency to your project:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>faux-pas</artifactId>
    <version>${faux-pas.version}</version>
</dependency>

Usage

Throwing functional interfaces

Faux Pas has a variant of every major functional interface from the Java core:

The followings statements apply to each of them:

  • extends the official interface, i.e. they are 100% compatible
  • sneakily throws the original exception

Creation

The way the Java runtime implemented functional interfaces always requires additional type information, either by using a cast or a local variable:

// compiler error
client::read.apply(name);

// too verbose
((ThrowingFunction<String, User, IOException>) client::read).apply(name);

// local variable may not always be desired
ThrowingFunction<String, User, IOException> readUser = client::read;
readUser.apply(name);

As a workaround there is a static factory method for every interface type inFauxPas. All of them are called throwingRunnable, throwingSupplier and so forth. It allows for concise one-line statements:

List<User> users = names.stream()
    .map(throwingFunction(client::read))
    .collect(toList());

Try-with-resources alternative

Traditional try-with-resources statements are compiled into byte code that includes unreachable parts and unfortunately JaCoCo has no support for filtering yet. That's why we came up with an alternative implementation. The official example for the try-with-resources statement looks like this:

try (BufferedReader br =
               new BufferedReader(new FileReader(path))) {
    return br.readLine();
}

Compared to ours:

return tryWith(new BufferedReader(new FileReader(path)), br -> 
    br.readLine()
);

CompletableFuture.exceptionally(Function)

CompletableFuture.exceptionally(..) is a very powerful but often overlooked tool. It allows to inject partial exception handling into a CompletableFuture:

future.exceptionally(e -> {
    Throwable t = e instanceof CompletionException ? e.getCause() : e;

    if (t instanceof NoRouteToHostException) {
        return fallbackValueFor(e);
    }

    throw e instanceof CompletionException ? e : new CompletionException(t);
})

Unfortunately it has a contract that makes it harder to use than it needs to:

In order to use the operation correctly one needs to follow these rules:

  1. Unwrap given throwable if it's an instance of CompletionException.
  2. Wrap checked exceptions in a CompletionException before throwing.

FauxPas.partially(..) relives some of the pain by changing the interface and contract a bit to make it more usable. The following example is functionally equivalent to the one from above:

future.exceptionally(partially(e -> {
    if (e instanceof NoRouteToHostException) {
        return fallbackValueFor(e);
    }

    throw e;
}))
  1. Takes a ThrowingFunction<Throwable, T, Throwable>, i.e. it allows clients to
    • directly re-throw the throwable argument
    • throw any exception during exception handling as-is
  2. Will automatically unwrap a CompletionException before passing it to the given function. I.e. the supplied function will never have to deal with CompletionException directly. Except for the rare occasion that the CompletionException has no cause, in which case it will be passed to the given function.
  3. Will automatically wrap any thrown Exception inside a CompletionException, if needed.

The last example is actually so common, that there is an overloaded version of partially that caters for this use particular case:

future.exceptionally(partially(NoRouteToHostException.class, this::fallbackValueFor))

CompletableFuture.whenComplete(BiConsumer)

future.whenComplete(failedWith(TimeoutException.class, e -> {
    request.cancel();
}))

Other missing pieces in CompletableFuture's API are exceptionallyCompose and handleCompose. Both can be seen as a combination of exceptionally + compose and handle + compose respectively. They basically allow to supply another CompletableFuture rather than concrete values directly. This is allows for asynchronous fallbacks:

exceptionallyCompose(users.find(name), e -> archive.find(name))

Getting Help

If you have questions, concerns, bug reports, etc., please file an issue in this repository's Issue Tracker.

Getting Involved/Contributing

To contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For more details, check the contribution guidelines.

Alternatives

faux-pas's People

Contributors

alexanderyastrebov avatar bocytko avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar dingxiangfei2009 avatar fatroom avatar gpradeepkrishna avatar mikaojk avatar semernitskaya avatar whiskeysierra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

faux-pas's Issues

Add support for CompletableFuture.exceptionally(Function<Throwable, T>)

Using this method to handle only specific exceptions requires to rethrow the original exception in a somewhat weird pattern:

throw e instanceof CompletionException ? (CompletionException) e : new CompletionException(e);

In addition, as usual, the functional interfaces from the JDK only allow to throw unchecked exceptions. Both issues could be supported by some utility higher-order function.

nested throwingFunction() doesn't work on AdoptOpenJDK

This is likely a bug in the OpenJDK, but I wanted to report it first here to see if you could give me a more definitive direction to investigate further, as well as indicate a workaround.

I'm using org.zalando.faux-pas.0.8.0 with OpenJDK 11 on Windows 10:

openjdk 11.0.5 2019-10-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)

I have a section of code that looks like this. Note that it has nested FauxPas throwingFunction(), although I don't know if the nesting has anything to do with it.

//look for a per-page navigation definition file in the form `.filename.ext.navigation.*`
final Optional<Stream<NavigationItem>> pageNavigationDefinition = pageFilename.flatMap(throwingFunction(filename -> {
  final Set<String> navigationFilenames = SUPPORTED_NAVIGATION_FILE_EXTENSIONS.stream()
      .map(ext -> addExtension(DOTFILE_PREFIX + filename + navigationBaseName, ext)).collect(toCollection(LinkedHashSet::new));
  return findAncestorFileByName(sourceDirectory, navigationFilenames, Files::isRegularFile, sourceDirectory)
      .map(throwingFunction(file -> loadNavigationFile(context, contextArtifact, file)));
}));

That compiles just fine on Eclipse 2019-12 (4.14.0). But on AdoptOpenJDK I get this:

unreported exception java.io.IOException; must be caught or declared to be thrown

This error refers to the following line from the code above, because loadNavigationFile() throws an IOException.

.map(throwingFunction(file -> loadNavigationFile(context, contextArtifact, file)));

This same problem occurs in another location, inside a throwingFunction() that is wrapped in a throwingSupplier().

Should this code be allowed? Which is correct here: the Eclipse compiler or the OpenJDK compiler?

And do you know of a workaround, apart from splitting the code sections out and making the stream logic harder to read?

Add overloaded version of partially

public static <T extends Throwable, R> Function<Throwable, R> partially(final Class<T> type, final ThrowingFunction<T, R, Throwable> function) {
    return partially(e -> {
        if (type.isInstance(e)) {
            return function.apply(type.cast(e));
        }
        throw e;
    });
}

This project seems to be heavily-inspired by throwing-function and doesn't comply with the license

This project seems to be heavily-inspired by throwing-function and doesn't comply with the original license.

throwing-function was released/published in February 2016, half a year before the first release of faux-pas

Description

faux-pas:

image

throwing-function:

image

This project seems to be heavily-inspired by throwing-function and doesn't comply with the license

Expected Behavior

The faux-pas project should be compliant with the license and not be a faux pas.

Actual Behavior

The faux-pas project is not compliant with the license and is a faux pas.

Possible Fix

I can think of at least two solutions:

Consider adding support for more functional interface types

List of candidates from java.util.function

  • BinaryOperator
  • BooleanSupplier
  • DoubleBinaryOperator
  • DoubleConsumer
  • DoubleFunction
  • DoublePredicate
  • DoubleSupplier
  • DoubleToIntFunction
  • DoubleToLongFunction
  • DoubleUnaryOperator
  • IntBinaryOperator
  • IntConsumer
  • IntFunction
  • IntPredicate
  • IntSupplier
  • IntToDoubleFunction
  • IntToLongFunction
  • IntUnaryOperator
  • LongBinaryOperator
  • LongConsumer
  • LongFunction
  • LongPredicate
  • LongSupplier
  • LongToDoubleFunction
  • LongToIntFunction
  • LongUnaryOperator
  • ObjDoubleConsumer
  • ObjIntConsumer
  • ObjLongConsumer
  • ToDoubleBiFunction
  • ToDoubleFunction
  • ToIntBiFunction
  • ToIntFunction
  • ToLongBiFunction
  • ToLongFunction
  • UnaryOperator

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.