Skip to content

Feature: Add Result<T, E> which represents a computation that may either result in a value (success) or an exception (failure) #3241

@jabrena

Description

@jabrena

Context:

In Java 8, the language added few functional programming Types like Optional<T> or CompletableFuture<T> (Promise) but not others like Either<L, R> which is included in this library.

Other modern programming language like Kotlin, Rust, Swift, Ocaml or F# add Result which was designed handle Error handling.

Goal:

Add Result<T> in the library.

Examples:

//Error handling easily
Function<String, Result<URI>> toURI = address -> {
    return Result.runCatching(() -> {
        return new URI(address);
    });
};

class ResultExampleTest {

    @Test
    void should_work() {
        List<String> endpoints = Arrays.asList(
            "https://jsonplaceholder.typicode.com/posts/1",
            "https://jsonplaceholder.typicode.com/posts/2",
            "https://jsonplaceholder.typicode.com/posts/3"
        );

        // @formatter:off

        List<Result<String>> results = endpoints.stream()
            .map(ResultExampleTest::fetchData)
            .toList();

        List<String> successfulResults = results.stream()
            .filter(Result::isSuccess)
            .map(Result::getValue)
            .flatMap(Optional::stream)
            .toList();

        // @formatter:on

        successfulResults.forEach(System.out::println);

        assertThat(successfulResults.size()).isGreaterThan(0);
    }

    private static Result<String> fetchData(String endpoint) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(endpoint)).build();

        return Result.runCatching(() -> {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() == 200) {
                return response.body();
            } else {
                throw new IOException("Failed to fetch data from " + endpoint);
            }
        });
    }
}

//Example using Railway-oriented programming
class ResultROPTest {

    Result<Integer> divide(int dividend, int divisor) {
        try {
            if (divisor == 0) {
                throw new IllegalArgumentException("Division by zero");
            }
            return Result.success(dividend / divisor);
        } catch (Exception e) {
            return Result.failure(e);
        }
    }

    Result<Integer> parseInteger(String input) {
        try {
            int parsedValue = Integer.parseInt(input);
            return Result.success(parsedValue);
        } catch (NumberFormatException e) {
            return Result.failure(e);
        }
    }

    // @formatter:off

    Result<Integer> calculate(String input1, String input2) {
        return parseInteger(input1)
                .flatMap(value1 -> parseInteger(input2)
                .flatMap(value2 -> divide(value1, value2)));
    }

    // @formatter:on

    @Test
    void should_work_ok() {
        Result<Integer> result = calculate("10", "2");
        int finalValue = result.getOrElse(() -> 0);

        assertThat(finalValue).isEqualTo(5);
    }

    @Test
    void should_work_ko() {
        Result<Integer> result = calculate("0", "2");
        int finalValue = result.getOrElse(() -> 0);

        assertThat(finalValue).isZero();
    }
}

Pending discussions:

  • What design is better? Result<T> or Result<T, E>
  • Add recommendations about when use Result and when use Either, Optional to minimize the usage of Java Exceptions
  • Implement the solution

Note: Result<T, E> it is quite similar to Either<L, R>

Discussion branch:

Other links:

Juan Antonio

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions