Result Type

Result.java is a custom type. Code can be found here It is heavily modeled off the Result Type that exists in other functional and ML Languages (ex: Elm, Haskell, Rust).

As described by the Rust documentation, “Result is a richer version of the Option type that describes possible error instead of possible absence.” Particularly in Java this gives the developer more control over when they would like to throw an error or bubble up an error.

Usage

Basics

A Result should be defined with the types of the potential successful object and the potential failure object. Ex: Result<Integer, String>. If the computation is successful it should should use .ok like Result.ok(5) and failures should be returned with error like Result.error("Unable to parse into integer").

A Result can also be Void meaning the computation was successful, but there was no return value. The type would be defined like this Result<Void, String>. Below is an example:

 if (creationIsSuccessful) {
   return Result.ok();
 } else {
   return Result.error("Could not create");
 }

To get the successful object. Please first check if the Result is successful by using the .isSuccessful() function. The successful object can be retrieved with .get(). Ex:

 Result<Integer, String> result = Result.ok(5)

 if (result.isSuccessful()) {
   Integer value = result.get();
 }

To get the error. Please first check if the Result is successful by using the .isSuccessful() function. The error object can be retrieved with .getError(). Ex:

 Result<Integer, String> result = Result.error("Could not parse")

 if (result.isSuccessful()) {
   Integer value = result.get();
 } else {
   String error = result.getError();
 }

Additional functionality

.map()

This should be used when transforming the successful type of a Result into another type. Ex: Result<Integer, String> -> Result<String, String>.

Result<Integer, String> originalResult = Result.ok(1);
Result<String, String> newResult =
  result.map(value -> Integer.toString(value));

-> newResult.isSuccessful() -> returns true
-> newResult.get() -> returns "1"

If the originalResult is an error the .map is not applied and originalResult remains unchanged. Ex:

Result<Integer, String> originalResult = Result.error("Error message");
Result<String, String> newResult = result.map(value -> value * 2);

-> newResult.isSuccessful() -> returns false
-> newResult.getError() -> returns "Error Message"
.andThen()

This should be used when chaining together functions that all return Result types and are conditional on the previous function call being successful.

Result<Integer, String> resultOne = Result.ok(1);

Result<String, String> newResult =
  resultOne.andThen(value -> Result.ok(Integer.toString(value)))
           .andThen(value -> Result.ok(value.concat(", 2, 3")));

-> newResult.isSuccessful() -> returns true
-> newResult.get() -> returns "1, 2, 3"

The function value -> Result.ok(value.concat(", 2, 3")) is conditional on the value being a String. If any of the functions return a Result.error none of the other functions are applied. Ex:

Result<ArrayList, String> newResult =
  resultOne.andThen(value -> Result.error("Error Message"))
           .andThen(value -> Result.ok(value.concat(", 2, 3")));

-> newResult.isSuccessful() -> returns false
-> newResult.getError() -> returns "Error Message"

The block .andThen(value -> Result.ok(value.concat(", 2, 3"))) does not run and instead the Result.error is returned.

If you are not concerned with the value returned the double underscore can be used to denote this. Ex:

Result<String, String> newResult = result.andThen(__ -> func1())
                                         .andThen(__ -> func2());

Summary

Instead of throwing exceptions, functions can return a Result type instead, which contains a corresponding error message. This allows the developer to decide when/ if they would like to throw in a top level method or display an appropriate error message to the user.

Additional Reading

Rust Result Type

Elm Result Type