Skip to content

Provides Result-type similar to Result-type in Rust that allows to return either successfull result or otherwise some kind of error and routines for interoperability of normal code that uses exception and functional code that uses Result-type

License

sviperll/result4j

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Result-type for Java

The project provides Result-type similar to Result-type in Rust that allows to return either successfull result or otherwise some kind of error.

In Java, the native way of reporting errors are exceptions, either checked or unchecked. You do not need Result-type most of the time in Java-code, where you can directly throw exceptions. But there are situations, where more functional-style is used. In such situations pure-functions are expected that throw no exceptions. Handling exception in such situations can be cumbersome and require a lot of boilerplate code. Result-type and associated helper-classes help with exception handling and allow to write idiomatic functional code that can interact with methods that throw exceptions.

Result-type provides a way to pass error enformation as a first-class value through the code written in functional style. Routines are provided for interoperability of normal code that uses exception and functional code that uses Result-type, so that exceptions can be caught and propagated as errors in Result-type and then rethrown again later in the control-flow.

Getting Started

result4j is available in Maven Central.

        <dependency>
            <groupId>com.github.sviperll</groupId>
            <artifactId>result4j</artifactId>
            <version>1.0</version>
        </dependency>

Overview

API Documentation is available for reference.

Result type can be either a successful result or some kind of error.

Result<String, E> suc = Result.success("Hello, World!");

The above line declares successful result value.

Result<String, Integer> err = Result.error(404);

The above line declares error-value.

Result<String, Integer> result = ...;
switch (result) {
    case Result.Success<String, Integer>(String value) -> System.out.println(value);
    case Result.Error<String, Integer>(Integer code) ->
            throw new IOException("%s: error".formatted(code));
}

Pattern matching can be used to check unknown result value as shown above.

Result<String, Integer> receivedResult = ...;
String value = receivedResult.throwError(code -> new IOException("%s: error".formatted(code)));
System.out.println(value);

Instead of a low-level pattern-matching, higher level helper-methods are available in Result-class. In the snippet above throwError is used to throw exception when Result contains error.

Result-type is created for interoperability between normal Java-code that throws exception and more functional code.

Catcher.ForFunctions<IOException> io =
    Catcher.of(IOException.class).forFunctions();
String concatenation =
        Stream.of("a.txt", "b.txt", "c.txt")
                .map(io.catching(name -> loadResource(name)))
                .collect(ResultCollectors.toSingleResult(Collectors.join()))
                .throwError(Function.identity());

Above code uses Catcher class to adapt functions that throw exceptions to return Result-type instead. ResultCollectors class contains helper-methods to collect multiple Results into a single one. You do not need Catcher class in normal Java-code, where you can directly throw exceptions. But the above snippet can serve as an example of situations, where more functional-style is used. In such situations pure-functions are expected that throw no exceptions. Handling exception in such situations can be cumbersome and require a lot of boilerplate code.

class MyMain {
    String loadResource(String name) throws IOException {
        // ...
    }

    void main(String[] args) throws IOException {
        Catcher.ForFunctions<IOException> io =
            Catcher.of(IOException.class).forFunctions();
        Function<String, Result<String, IOException>> f = io.catching(MyMain::loadResult);
        Result<String, IOException> result = f.apply("my-resource");
        String value = result.throwError(Function.identity());
        System.out.println(value);
    }
}

The example above shows the usage of the catching method of the Catcher class, that allows to adapt exception throwing method and instead to have a method that returns Result with exception representing as an error-value.

There is also an AdaptingCatcher class that allows to adapt or wrap exceptions.

AdaptingCatcher.ForFunctions<IOException, PipelineException> io =
        Catcher.of(IOException.class).map(PipelineException::new).forFunctions();
AdaptingCatcher.ForFunctions<MLException, PipelineException> ml =
        Catcher.of(MLException.class).map(PipelineException::new).forFunctions();
List<Animal> animals1 =
        List.of("cat.jpg", "dog.jpg")
                .stream()
                .map(io.catching(Fakes::readFile))
                .map(Result.flatMapping(ml.catching(Fakes::recognizeImage)))
                .collect(ResultCollectors.toSingleResult(Collectors.toList()))
                .throwError(Function.identity());
Assertions.assertEquals(List.of(Animal.CAT, Animal.DOG), animals1);

About

Provides Result-type similar to Result-type in Rust that allows to return either successfull result or otherwise some kind of error and routines for interoperability of normal code that uses exception and functional code that uses Result-type

Resources

License

Stars

Watchers

Forks

Packages

No packages published