don't worry, it's probably fine

Quick and Dirty Result Type in Java 15

02 Sep 2020

java

The final release candidate for Java 15 was released at the end of August. I downloaded it to take its new preview features for a spin - primarily, Sealed Classes.

In brief, a sealed class can declare exactly the classes to which it permits extension or implementation, and it is “sealed” against any other extensions or implementations.

Taking inspiration from a former colleague’s excellent series of blogposts on Railway-Oriented Programming, I hacked together a quick Result<L,R> type using a sealed interface and records to implement Success<> and Failure<> types.

Implementation

import java.util.function.Function;

public sealed interface Result<LEFT, RIGHT> permits Result.Success, Result.Failure {

    // Both functions have a common result supertype
    // e.g. `T` can be a `Result<X,Y>` or a resolved type like a `String` / `Request`
    <T, L2 extends T, R2 extends T> T either(Function<LEFT, L2> left, Function<RIGHT, R2> right);

    default <T> T then(Function<Result<LEFT, RIGHT>, T> function) {
        return function.apply(this);
    }

    record Success<L, R>(L value) implements Result<L, R> {
        @Override
        public <T, L2 extends T, R2 extends T> T either(Function<L, L2> left, Function<R, R2> right) {
            return left.apply(value);
        }
    }

    record Failure<L, R>(R error) implements Result<L, R> {
        @Override
        public <T, L2 extends T, R2 extends T> T either(Function<L, L2> left, Function<R, R2> right) {
            return right.apply(error);
        }
    }
}

Example

public class ResultTest {

    @Test
    public void example() {
        var output = parse("ten")
                .then(this::attemptToDouble);

        // Expected: is "Two times ten is equal to 20"
        //     but: was "java.lang.NumberFormatException: For input string: \"ten\""
        assertThat(output, is("Two times ten is equal to 20"));
    }

    public Result<Integer, Exception> parse(String input) {
        try {
            return new Success<>(Integer.parseInt(input));
        } catch (NumberFormatException e) {
            return new Failure<>(e);
        }
    }

    private String attemptToDouble(Result<Integer, Exception> result) {
        return result.either(
                integer -> "Two times ten is equal to " + integer * 2,
                Throwable::toString
        );
    }
}

You can view this example as a Gist with syntax-highlighting.