In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-01 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article is about how to use Either and Option for functional error handling. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.
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 alternative ways to express error conditions. In this article, I'll demonstrate the type-safe error handling mechanism in Java, which bypasses the normal exception propagation mechanism (and is illustrated by some examples of the Functional Java framework).
Functional error handling
If you want to handle errors without using exceptions in Java, the most fundamental obstacle is the language limitation, because the method can only return a single value. But, of course, a method can return a single Object (or subclass) reference that can contain multiple values. Then, I can use a single Map to enable multiple return values. Look at the pide () method in listing 1:
Listing 1. Use Map to process multiple return values
Public static Map pide (int x, int y) {Map result = new HashMap (); if (y = = 0) result.put ("exception", new Exception ("p by zero")); elseresult.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 pide () method, I output exception to indicate failure, or output answer to indicate success. Both modes are tested in listing 2:
Listing 2. Testing successes and failures using Map
@ Testpublic void maps_success () {Map result = RomanNumeralParser.pide (4,2); assertEquals (2.0,( Double) result.get ("answer"), 0.1);} @ Testpublic void maps_failure () {Map result = RomanNumeralParser.pide (4,0); assertEquals ("p 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 anomalies.
There are some obvious problems with this approach. First, the results in Map are by no means type safe, and it disables the compiler's ability to catch specific errors. The enumeration of keys can improve this situation slightly, but with little effect. Second, the method caller does not know whether the method call was successful or not, which adds to the burden on the caller to check the dictionary of possible results. Third, nothing can stop 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.
Either class
The need to return two different values often occurs in functional languages, and 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 Either class
Public class Either {private A left = null;private B right = null;private Either (A return right; B) {left = a TX right = b;} public static Either left (An a) {return new Either (a);} public A left () {return left;} public boolean isLeft () {return left! = null;} public boolean isRight () {return right! = null;} public B right () {return right;} public static Either right (BB) {return new Either (null,b) } public void fold (F leftOption, F rightOption) {if (right = = null) leftOption.f (left); elserightOption.f (right);}}
In listing 3, Either is designed to save a left or right value (but never both). This data structure is called disjoint union. Some C-based languages contain union data types, which can hold an instance of several different types. Slots that do not intersect and join can save two types, but only one instance of one type. The Either class has a private constructor that makes the construction the responsibility of static methods left (AA) or right (BB). Other methods in the class are helper programs that are responsible for retrieving and investigating members of the class.
With Either, I can write code to return an exception or a legitimate result (but never both) while keeping it type safe. A common functional convention is that the left of the Either 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 RomanNumeral class. The parseNumber () method and the declarative test are shown in listing 4:
Listing 4. Analysis of Roman numerals
Public static Either parseNumber (String s) {if (! S.matches ("[IVXLXCDM] +") return Either.left (new Exception ("Invalid Roman numeral")); elsereturn Either.right (new RomanNumeral (s). ToInt ());} @ Testpublic void parsing_success () {Either result = RomanNumeralParser.parseNumber ("XLII"); assertEquals (Integer.valueOf (42), result.right ());} @ Testpublic void parsing_failure () {Either result = RomanNumeralParser.parseNumber ("FOO"); assertEquals (INVALID_ROMAN_NUMERAL, result.left (). GetMessage ());}
In listing 4, the parseNumber () method performs a validation (to display the error), placing the error condition in the left of the Either, or the result in its right. These two situations are shown in the unit test.
This is a big improvement over passing Map around. I keep the type safe (note that I can make the exception as specific as I like); in the method declaration through generics, the error is obvious; the result returned has an extra indirect level that can decompress the result of Either (whether it is an exception or an answer). Additional indirect levels 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 it is intended to work with other Functional Java constructs. Therefore, I can use a combination of Either and Functional Java's P1 classes to create lazy error assessments. A lazy expression is an expression that executes on demand (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 and P3, etc., include a variety of methods. P1 is used in Functional Java to pass a block of code without executing it, enabling you to execute code in the context of your choice.
In Java, as long as you throw an exception, the exception is instantiated. By returning a lazy evaluation method, I can postpone exception creation until later. Take a look at the example and related tests in listing 5:
Listing 5. Create a lazy parser using Functional Java
Public static P1 parseNumberLazy (final String s) {if (! S.matches ("[IVXLXCDM] +") return new P1 () {public Either _ 1 () {return Either.left (new Exception ("Invalid Roman numeral"));}}; elsereturn new P1 () {public Either _ 1 () {return Either.right (new RomanNumeral (s). ToInt ());}};} @ Testpublic void parse_lazy () {P1 result = FjRomanNumeralParser.parseNumberLazy ("XLII"); assertEquals ((long) 42, (long) result._1 (). Right (). Value ()) } @ Testpublic void parse_lazy_exception () {P1 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 that in listing 4, but with an extra P1 wrapper. In the parse_lazy test, I have to extract 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 to see if a left exists, and I can extract the exception to identify its messages.
Until you call _ 1 () to extract Either's left, the exception (along with its expensive stack trace generation) will not be created. Therefore, the exception is lazy, allowing you to postpone the execution of the exception's constructor.
Provide default values
Laziness is not the only benefit of using Either for error handling. Another benefit is that you can provide default values. Look at the code in listing 6:
Listing 6. Provide a reasonable default return value
Public static Either 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 ();} @ Testpublic void parse_defaults_normal () {Either result = FjRomanNumeralParser.parseNumberDefaults ("XLII"); assertEquals ((long) 42, (long) result.right (). Value ());} @ Testpublic void parse_defaults_triggered () {Either result = FjRomanNumeralParser.parseNumberDefaults ("MM"); assertEquals ((long) 1000, (long) result.right (). Value ());}
In listing 6, assuming that I do not accept any Roman numerals greater than MAX, any number that attempts to be greater than this value will be set to MAX by default. The parseNumberDefaults () method ensures that the default value is placed in the right of Either.
Abnormal packing
I can also use Either to wrap exceptions and convert structured exception handling into functions, as shown in listing 7:
Listing 7. Catch other people's exceptions
Public static Either pide (int x, int y) {try {return Either.right (x / y);} catch (Exception e) {return Either.left (e);}} @ Testpublic void catching_other_people_exceptions () {Either result = FjRomanNumeralParser.pide (4,2); assertEquals ((long) 2, (long) result.right (). Value ()); Either failure = FjRomanNumeralParser.pide (4,0); assertEquals ("/ by zero", failure.left (). Value (). GetMessage ();}
In listing 7, I try division, which may trigger an ArithmeticException. If an exception occurs, I wrap it in the left of Either; otherwise I return the result in right. Using Either allows you to convert traditional exceptions (including checked exceptions) 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 pideLazily (final int x, final int y) {return new P1 () {public Either _ 1 () {try {return Either.right (x / y);} catch (Exception e) {return Either.left (e);};} @ Testpublic void lazily_catching_other_people_exceptions () {P1 result = FjRomanNumeralParser.pideLazily (4,2); assertEquals ((long) 2, (long) result._1 (). Right (). Value ()); P1 failure = FjRomanNumeralParser.pideLazily (4,0) AssertEquals ("/ by zero", failure._1 (). Left (). Value (). GetMessage ());}
Nested exception
A nice feature of Java exceptions is the ability to declare several different potential exception types as part of a method signature. Although the syntax is becoming more and more complex, Either can also do this. For example, if I need a method on RomanNumeralParser to allow me to divide two Roman numerals, but I need to return two different possible exceptions, is it a parsing error or a division error? Using standard Java generics, I can nest exceptions, as shown in listing 9:
Listing 9. Nested exception
Public static Either pideRoman (final String x, final String y) {Either possibleX = parseNumber (x); Either possibleY = parseNumber (y); if (possibleX.isLeft () | | possibleY.isLeft ()) return Either.left (new NumberFormatException ("invalid parameter")); int intY = possibleY.right (). Value (). IntValue (); Either errorForY = Either.left (new ArithmeticException ("p by 1")); if (intY = = 1) return Either.right ((fj.data.Either) errorForY) Int intX = possibleX.right (). Value (). IntValue (); Either result = Either.right (new Double ((double) intX) / intY); return Either.right (result);} @ Testpublic void test_pide_romans_success () {fj.data.Either result = FjRomanNumeralParser.pideRoman ("IV", "II"); assertEquals (2.0 work result right (). Value (). Right (). Value (). DoubleValue () } @ Testpublic void test_pide_romans_number_format_error () {Either result = FjRomanNumeralParser.pideRoman ("IVooo", "II"); assertEquals ("invalid parameter", result.left (). Value (). GetMessage ();} @ Testpublic void test_pide_romans_arthmetic_exception () {Either result = FjRomanNumeralParser.pideRoman ("IV", "I"); assertEquals ("p by 1", result.right (). Value (). Left (). Value (). GetMessage ());}
In listing 9, the pideRoman () method first decompresses the Either returned from the original parseNumber () method in listing 4. If an exception occurs in either of these two digital conversions, Either left returns with the exception. Next, I have to extract the actual integer values and then perform other validation criteria. Roman numerals don't have the concept of zero, so I made a rule that divisor is not allowed to be 1: if the denominator is 1, I package my exception and put it in the left of right.
In other words, I have three slots, divided by type: NumberFormatException, ArithmeticException, and Double. The first Either's left holds the potential NumberFormatException, and its right holds another Either. The left of the second Either contains a potential ArithmeticException, and its right contains the payload, the result. So, in order to get the actual answer, I have to iterate through result.right (). Value (). Right (). Value (). DoubleValue ()! Obviously, the usefulness of this approach collapses quickly, but it does provide a type-safe way to nest exceptions as part of a class signature.
Option class
Either is a convenient concept that I'll use to build tree data structures in the next article. There is a similar class in Scala called Option, which is copied in Functional Java, providing a simpler exception: none represents an illegal value, and some indicates a successful return. The Option is shown in listing 10:
Listing 10. Use Option
Public static Option pide (double x, double y) {if (y = 0) return Option.none (); return Option.some (x / y);} @ Testpublic void option_test_success () {Option result = FjRomanNumeralParser.pide (4.0,2); assertEquals (2.0, (Double) result.some (), 0.1);} @ Testpublic void option_test_failure () {Option result = FjRomanNumeralParser.pide (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.
Either and Option in Functional Java are both monomers, which represent the special data structure of computation and are widely used in functional languages. In the next installment, I'll explore the monomer concept of Either and demonstrate how it supports Scala-style pattern matching in different examples.
Thank you for reading! On "how to use Either and Option for functional error handling" this article is shared here, I hope the above content can be of some help to you, so that you can learn more knowledge, if you think the article is good, you can share it out for more people to see it!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un