How to use either and option for functional error handling

Time:2019-11-29

Preface

I’ll talk about Scala style pattern matching, but first I need to build some background through the concept of either. One use of either is functional style error handling, which I’ll cover in this installment.

In Java, error handling is traditionally supported by exceptions and languages that create and propagate exceptions. But what if there is no structured exception handling? Many functional languages do not support exception paradigms, so they must find alternatives to expressing error conditions. In this article, I will demonstrate the type safe error handling mechanism in Java, which bypasses the normal exception propagation mechanism (and is illustrated with some examples of the functional Java framework).

Functional error handling

If you want to handle errors without exceptions in Java, the fundamental obstacle is the limitation of the language, because methods can only return a single value. However, of course, methods can return a single object (or subclass) reference, which can contain multiple values. Then, I can use a map to enable multiple return values. Look at the divide () method in Listing 1:

Listing 1. Using map to process multiple return values


public static Map<String, Object> divide(int x, int y) {
Map<String, Object> result = new HashMap<String, Object>();
if (y == 0)
result.put("exception", new Exception("div by zero"));
else
result.put("answer", (double) x / y);
return result;
}

In Listing 1, I create a map with string as the key and object as the value. In the divide () method, I output exception for failure or answer for success. Both patterns are tested in Listing 2:

Listing 2. Test success and failure with map


@Test
public void maps_success() {
Map<String, Object> result = RomanNumeralParser.divide(4, 2);
assertEquals(2.0, (Double) result.get("answer"), 0.1);
}
@Test
public void maps_failure() {
Map<String, Object> result = RomanNumeralParser.divide(4, 0);
assertEquals("div by zero", ((Exception) result.get("exception")).getMessage());
}

In Listing 2, the maps’success test verifies that the correct entry exists in the returned map. The maps? Failure test checks for exceptions.

There are some obvious problems with this method. First, the results in a map are not type safe anyway, and it disables the compiler’s ability to catch specific errors. Enumeration of keys can slightly improve this situation, but not much. Secondly, the method caller does not know whether the method call is successful or not, which increases the burden of the calling program. It needs to check the dictionary of possible results. Third, nothing prevents both keys from having values, which makes the results ambiguous.

What I need is a mechanism that allows me to return two (or more) values in a type safe manner.

Class Either

The need to return two different values is often found in functional languages. A common data structure used to simulate this behavior is the either class. In Java, I can use generics to create a simple either class, as shown in Listing 3:

Listing 3. Return two (type safe) values through the ether class


public class Either<A,B> {
private A left = null;
private B right = null;
private Either(A a,B b) {
left = a;
right = b;
}
public static <A,B> Either<A,B> left(A a) {
return new Either<A,B>(a,null);
}
public A left() {
return left;
}
public boolean isLeft() {
return left != null;
}
public boolean isRight() {
return right != null;
}
public B right() {
return right;
}
public static <A,B> Either<A,B> right(B b) {
return new Either<A,B>(null,b);
}
public void fold(F<A> leftOption, F<B> rightOption) {
if(right == null)
leftOption.f(left);
else
rightOption.f(right);
}
}

In Listing 3, either is designed to hold a left or right value (but never both). This data structure is called disjoint union. Some C-based languages contain the Union data type, which can hold an instance with several different types. Slots that do not intersect and union can hold two types, but only one instance of one type. The either class has a private constructor that makes the construct the responsibility of the static method left (a) or right (b). Other methods in a class are helper programs that retrieve and investigate members of the class.

With either, I can write code to return an exception or a valid result (but never both), while keeping the type safe. A common functional convention is that left of the ether class contains exceptions, if any, and right contains results.

Analysis of Roman numerals

I have a class called romannumeral (I leave its implementation to the reader’s imagination) and a class called romannumeralparser, which calls the romannumeralparser class. The parsenumber () method and illustrative tests are shown in Listing 4:

Listing 4. Parsing roman numbers


public static Either<Exception, Integer> parseNumber(String s) {
if (! s.matches("[IVXLXCDM]+"))
return Either.left(new Exception("Invalid Roman numeral"));
else
return Either.right(new RomanNumeral(s).toInt());
}
@Test
public void parsing_success() {
Either<Exception, Integer> result = RomanNumeralParser.parseNumber("XLII");
assertEquals(Integer.valueOf(42), result.right());
}
@Test
public void parsing_failure() {
Either<Exception, Integer> result = RomanNumeralParser.parseNumber("FOO");
assertEquals(INVALID_ROMAN_NUMERAL, result.left().getMessage());
}

In Listing 4, the parsenumber () method performs a validation (to show the error), places the error condition in either either’s left, or places the result in its right. Both are shown in unit tests.

This is a big improvement over passing maps around. I keep the type safe (note that I can make exceptions as specific as I like); in generic method declarations, the error is obvious; the returned result has an additional level of indirection to decompress the result of either (exception or answer). Additional levels of indirection support inertia.

Lazy parsing and functional Java

The either class appears in many functional algorithms and is so common in the functional world that the functional Java framework (see resources) also includes an either implementation that will be used in the examples in listings 3 and 4. But its purpose is to work with other functional Java constructs. Therefore, I can use the P1 class of either and functional java to create lazy error evaluation. An lazy expression is an on-demand expression (see resources).

In functional Java, the P1 class is a simple wrapper that includes a method named_1(), which takes no parameters. (other variants: P2, P3, etc., include multiple methods.) P1 is used in functional java to pass a code block without executing it, enabling you to execute code in the context of your choice.

In Java, whenever you throw an exception, the exception is instantiated. By returning a method of lazy evaluation, I can defer exception creation until later. Take a look at the example and related tests in Listing 5:

Listing 5. Creating an inert parser using functional Java


public static P1<Either<Exception, Integer>> parseNumberLazy(final String s) {
if (! s.matches("[IVXLXCDM]+"))
return new P1<Either<Exception, Integer>>() {
public Either<Exception, Integer> _1() {
return Either.left(new Exception("Invalid Roman numeral"));
}
};
else
return new P1<Either<Exception, Integer>>() {
public Either<Exception, Integer> _1() {
return Either.right(new RomanNumeral(s).toInt());
}
};
}
@Test
public void parse_lazy() {
P1<Either<Exception, Integer>> result = FjRomanNumeralParser.parseNumberLazy("XLII");
assertEquals((long) 42, (long) result._1().right().value());
}
@Test
public void parse_lazy_exception() {
P1<Either<Exception, Integer>> result = FjRomanNumeralParser.parseNumberLazy("FOO");
assertTrue(result._1().isLeft());
assertEquals(INVALID_ROMAN_NUMERAL, result._1().left().value().getMessage());
}

The code in Listing 5 is similar to listing 4, but with an additional P1 wrapper. In the parse_zytest, I have to decompress the result by calling_1() on the result, which returns the right of either, from which I can retrieve the value. In the parse  lazy  exception test, I can check if there is a left, and I can extract the exception to identify its message.

The exception (along with its expensive stack trace generation) will not be created until you call_1() to extract either’s left. As a result, exceptions are lazy, allowing you to delay the execution of the constructor of the exception.

Provide default values

Inertia is not the only benefit of using either for error handling. Another benefit is that you can provide default values. See the code in Listing 6:

Listing 6. Providing a reasonable default return value


public static Either<Exception, Integer> parseNumberDefaults(final String s) {
if (! s.matches("[IVXLXCDM]+"))
return Either.left(new Exception("Invalid Roman numeral"));
else {
int number = new RomanNumeral(s).toInt();
return Either.right(new RomanNumeral(number >= MAX ? MAX : number).toInt());
}
}
@Test
public void parse_defaults_normal() {
Either<Exception, Integer> result = FjRomanNumeralParser.parseNumberDefaults("XLII");
assertEquals((long) 42, (long) result.right().value());
}
@Test
public void parse_defaults_triggered() {
Either<Exception, Integer> result = FjRomanNumeralParser.parseNumberDefaults("MM");
assertEquals((long) 1000, (long) result.right().value());
}

In Listing 6, assuming I don’t accept any roman numbers greater than max, any number that attempts to be greater than that will be set to Max by default. The parsenumberdefaults() method ensures that the default value is placed in the right of either.

Packaging exception

I can also use either to wrap exceptions and convert structured exception handling to functional, as shown in Listing 7:

Listing 7. Catching someone else’s exception


public static Either<Exception, Integer> divide(int x, int y) {
try {
return Either.right(x / y);
} catch (Exception e) {
return Either.left(e);
}
}
@Test
public void catching_other_people_exceptions() {
Either<Exception, Integer> result = FjRomanNumeralParser.divide(4, 2);
assertEquals((long) 2, (long) result.right().value());
Either<Exception, Integer> failure = FjRomanNumeralParser.divide(4, 0);
assertEquals("/ by zero", failure.left().value().getMessage());
}

In Listing 7, I try to divide, which can cause an arithmeticexception. If an exception occurs, I wrap it in either’s left; otherwise, I return the result in right. Using either enables you to convert traditional exceptions, including checked ones, into a more functional style.

Of course, you can also lazily wrap the exception thrown from the called method, as shown in Listing 8:

Listing 8. Lazy catch exception


public static P1<Either<Exception, Integer>> divideLazily(final int x, final int y) {
return new P1<Either<Exception, Integer>>() {
public Either<Exception, Integer> _1() {
try {
return Either.right(x / y);
} catch (Exception e) {
return Either.left(e);
}
}
};
}
@Test
public void lazily_catching_other_people_exceptions() {
P1<Either<Exception, Integer>> result = FjRomanNumeralParser.divideLazily(4, 2);
assertEquals((long) 2, (long) result._1().right().value());
P1<Either<Exception, Integer>> failure = FjRomanNumeralParser.divideLazily(4, 0);
assertEquals("/ by zero", failure._1().left().value().getMessage());
}

nested exception

Java exceptions have a nice feature that can declare several different types of potential exceptions as part of a method signature. Despite the increasing complexity of syntax, either can do the same. For example, if I need a method on romannumeralparser that allows me to divide two roman numbers, but I need to return two different possible exceptions, is it a parse error or a division error? Using standard Java generics, I can nest exceptions, as shown in listing 9:

Listing 9. Nesting exception


public static Either<NumberFormatException, Either<ArithmeticException, Double>> 
divideRoman(final String x, final String y) {
Either<Exception, Integer> possibleX = parseNumber(x);
Either<Exception, Integer> possibleY = parseNumber(y);
if (possibleX.isLeft() || possibleY.isLeft())
return Either.left(new NumberFormatException("invalid parameter"));
int intY = possibleY.right().value().intValue();
Either<ArithmeticException, Double> errorForY = 
Either.left(new ArithmeticException("div by 1"));
if (intY == 1)
return Either.right((fj.data.Either<ArithmeticException, Double>) errorForY);
int intX = possibleX.right().value().intValue();
Either<ArithmeticException, Double> result = 
Either.right(new Double((double) intX) / intY);
return Either.right(result);
}
@Test
public void test_divide_romans_success() {
fj.data.Either<NumberFormatException, Either<ArithmeticException, Double>> result = 
FjRomanNumeralParser.divideRoman("IV", "II");
assertEquals(2.0,result.right().value().right().value().doubleValue(), 0.1);
}
@Test
public void test_divide_romans_number_format_error() {
Either<NumberFormatException, Either<ArithmeticException, Double>> result = 
FjRomanNumeralParser.divideRoman("IVooo", "II");
assertEquals("invalid parameter", result.left().value().getMessage());
}
@Test
public void test_divide_romans_arthmetic_exception() {
Either<NumberFormatException, Either<ArithmeticException, Double>> result = 
FjRomanNumeralParser.divideRoman("IV", "I");
assertEquals("div by 1", result.right().value().left().value().getMessage());
}

In listing 9, the dividelman () method first decompresses the ether returned from the original parsenumber () method in Listing 4. If an exception occurs in either of these two numeric conversions, either left is returned with the exception. Next, I have to extract the actual integer value and then perform other validation criteria. Roman numbers don’t have a zero concept, so I made a rule that I don’t allow divisors to be 1: if the denominator is 1, I pack my exceptions and put them in the right left.

In other words, I have three slots by type: numberformatexception, arithmeticexception, and double. The left of the first either holds the potential numberformatexception, and its right holds the other either. The second either’s left contains a potential arithmeticexception whose right contains the payload, the result. Therefore, in order to get the actual answer, I have to traverse result. Right(). Value(). Right(). Value(). Doublevalue()! Obviously, the utility of this method quickly disintegrates, but it does provide a type safe way to nest exceptions as part of a class signature.

Class Option

Either is a convenient concept, which I will use to build a tree data structure in the next article. In Scala, there is a class named option, which is copied in functional Java, providing a simpler exception: none represents an illegal value, and some represents a successful return. Option is shown in listing 10:

Listing 10. Using option


public static Option<Double> divide(double x, double y) {
if (y == 0)
return Option.none();
return Option.some(x / y);
}
@Test
public void option_test_success() {
Option result = FjRomanNumeralParser.divide(4.0, 2);
assertEquals(2.0, (Double) result.some(), 0.1);
}
@Test
public void option_test_failure() {
Option result = FjRomanNumeralParser.divide(4.0, 0);
assertEquals(Option.none(), result);
}

As shown in listing 10, option contains none () or some (), similar to left and right in either, but specific to methods that may not have a legal return value.

Both either and option in functional Java are monomers, representing the special data structure of calculation, which are widely used in functional languages. In the next installment, I’ll explore the monomer concept of either and demonstrate in different examples how it supports Scala style pattern matching.

Concluding remarks

When you learn a new paradigm, you need to rethink all the familiar problem-solving methods. Functional programming uses different idioms to report error conditions, most of which can be copied in Java. Undeniably, there are also some puzzling syntax.

The above is the whole content of this article. I hope it will help you in your study, and I hope you can support developepaer more.