91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎樣利用Either和Option進行函數式錯誤處理的示例

發布時間:2021-02-04 11:30:21 來源:億速云 閱讀:421 作者:小新 欄目:編程語言

這篇文章將為大家詳細講解有關怎樣利用Either和Option進行函數式錯誤處理的示例,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

前言

我將討論 Scala 風格的模式匹配,但首先我需要通過 Either 概念建立一些背景知識。Either 的其中一個用法是函數式風格的錯誤處理,我會在本期文章中對其進行介紹。

在 Java 中,錯誤的處理在傳統上由異常以及創建和傳播異常的語言支持進行。但是,如果不存在結構化異常處理又如何呢?許多函數式語言不支持異常范式,所以它們必須找到表達錯誤條件的替代方式。在本文中,我將演示 Java 中類型安全的錯誤處理機制,該機制繞過正常的異常傳播機制(并通過 Functional Java 框架的一些示例協助說明)。

函數式錯誤處理

如果您想在 Java 中不使用異常來處理錯誤,最根本的障礙是語言的限制,因為方法只能返回單個值。但是,當然,方法可以 返回單個 Object(或子類)引用,其中可包含多個值。那么,我可以使用一個 Map 來啟用多個返回值。請看看清單 1 中的 divide() 方法:

清單 1. 使用 Map 處理多個返回值

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;
}

在 清單 1 中,我創建了一個 Map,以 String 為鍵,并以 Object 為值。在 divide() 方法中,我輸出 exception 來表示失敗,或者輸出 answer 來表示成功。清單 2 中對兩種模式都進行了測試:

清單 2. 使用 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());
}

在 清單 2 中,maps_success 測試驗證在返回的 Map 中是否存在正確的條目。maps_failure 測試檢查異常情況。

這種方法有一些明顯的問題。首先,Map 中的結果無論如何都不是類型安全的,它禁用了編譯器捕獲特定錯誤的能力。鍵的枚舉可以略微改善這種情況,但效果不大。其次,該方法調用器并不知道方法調用是否成功,這加重了調用程序的負擔,它要檢查可能結果的詞典。第三,沒有什么能阻止這兩個鍵都有值,這使得結果模棱兩可。

我需要的是一種讓我能夠以類型安全的方式返回兩個(或多個)值的機制。

Either 類

返回兩個不同值的需求經常出現在函數式語言中,用來模擬這種行為的一個常用數據結構是 Either 類。在 Java 中,我可以使用泛型創建一個簡單的 Either 類,如清單 3 所示:

清單 3. 通過 Either 類返回兩個(類型安全的)值

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);
}
}

在 清單 3中,Either 旨在保存一個 left 或 right 值(但從來都不會同時保存這兩個值)。該數據結構被稱為不相交并集。一些基于 C 的語言包含 union 數據類型,它可以保存含若干種不同類型的一個實例。不相交并集的槽可以保存兩種類型,但只保存其中一種類型的一個實例。Either 類有一個 private 構造函數,使構造成為靜態方法 left(A a) 或 right(B b) 的責任。在類中的其他方法是輔助程序,負責檢索和調研類的成員。

利用 Either,我可以編寫代碼來返回異常或 一個合法結果(但從來都不會同時返回兩種結果),同時保持類型安全。常見的函數式約定是 Either 類的 left 包含異常(如有),而 right 包含結果。

解析羅馬數字

我有一個名為 RomanNumeral 的類(我將其實現留給讀者去想象)和一個名為 RomanNumeralParser 的類,該類調用 RomanNumeral 類。parseNumber() 方法和說明性測試如清單 4 所示:

清單 4. 解析羅馬數字

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());
}

在 清單 4 中,parseNumber() 方法執行一個驗證(用于顯示錯誤),將錯誤條件放置在 Either 的 left 中,或將結果放在它的 right中。單元測試中顯示了這兩種情況。

比起到處傳遞 Map,這是一個很大的改進。我保持類型安全(請注意,我可以按自己喜歡使異常盡量具體);在通過泛型的方法聲明中,錯誤是明顯的;返回的結果帶有一個額外的間接級別,可以解壓 Either 的結果(是異常還是答案)。額外的間接級別支持惰性。

惰性解析和 Functional Java

Either 類出現在許多函數式算法中,并且在函數式世界中如此之常見,以致 Functional Java 框架(參閱 參考資料)也包含了一個 Either 實現,該實現將在 清單 3 和 清單 4 的示例中使用。但它的目的就是與其他 Functional Java 構造配合使用。因此,我可以結合使用 Either 和 Functional Java 的 P1 類來創建惰性 錯誤評估。惰性表達式是一個按需執行的表達式(參閱 參考資料)。

在 Functional Java 中,P1 類是一個簡單的包裝器,包括名為 _1() 的方法,該方法不帶任何參數。(其他變體:P2 和 P3 等,包含多種方法。)P1 在 Functional Java 中用于傳遞一個代碼塊,而不執行它,使您能夠在自己選擇的上下文中執行代碼。

在 Java 中,只要您 throw 一個異常,異常就會被實例化。通過返回一個惰性評估的方法,我可以將異常創建推遲到以后。請看看清單 5 中的示例及相關測試:

清單 5. 使用 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());
}

清單 5 中的代碼與 清單 4 中的類似,但多了一個 P1 包裝器。在 parse_lazy 測試中,我必須通過在結果上調用 _1() 來解壓結果,該方法返回 Either 的 right,從該返回值中,我可以檢索值。在 parse_lazy_exception 測試中,我可以檢查是否存在一個 left,并且我可以解壓異常,以辨別它的消息。

在您調用 _1() 解壓 Either 的 left 之前,異常(連同其生成成本昂貴的堆棧跟蹤)不會被創建。因此,異常是惰性的,讓您推遲異常的構造程序的執行。

提供默認值

惰性不是使用 Either 進行錯誤處理的惟一好處。另一個好處是,您可以提供默認值。請看清單 6 中的代碼:

清單 6. 提供合理的默認返回值

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());
}

在 清單 6 中,假設我不接受任何大于 MAX 的羅馬數字,任何企圖大于該值的數字都將被默認設置為 MAX。parseNumberDefaults() 方法確保默認值被放置在 Either 的 right 中。

包裝異常

我也可以使用 Either 來包裝異常,將結構化異常處理轉換成函數式,如清單 7 所示:

清單 7. 捕獲其他人的異常

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());
}

在 清單 7 中,我嘗試除法,這可能引發一個 ArithmeticException。如果發生異常,我將它包裝在 Either 的 left 中;否則我在 right 中返回結果。使用 Either 使您可以將傳統的異常(包括檢查的異常)轉換成更偏向于函數式的風格。

當然,您也可以惰性包裝從被調用的方法拋出的異常,如清單 8 所示:

清單 8. 惰性捕獲異常

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());
}

嵌套異常

Java 異常有一個不錯的特性,它能夠將若干種不同的潛在異常類型聲明為方法簽名的一部分。盡管語法越來越復雜,但 Either 也可以做到這一點。例如,如果我需要 RomanNumeralParser 上的一個方法允許我對兩個羅馬數字執行除法,但我需要返回兩種不同的可能異常情況,那么是解析錯誤還是除法錯誤?使用標準的 Java 泛型,我可以嵌套異常,如清單 9 所示:

清單 9. 嵌套異常

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());
}

在 清單 9 中,divideRoman() 方法首先解壓從 清單 4 的原始 parseNumber() 方法返回的 Either。如果在這兩次數字轉換的任一次中發生一個異常,Either left 與異常一同返回。接下來,我必須解壓實際的整數值,然后執行其他驗證標準。羅馬數字沒有零的概念,所以我制定了一個規則,不允許除數為 1:如果分母是 1,我打包我的異常,并放置在 right 的 left 中。

換句話說,我有三個槽,按類型劃分:NumberFormatException、ArithmeticException 和 Double。第一個 Either 的 left 保存潛在的 NumberFormatException,它的 right 保存另一個 Either。第二個 Either 的 left 包含一個潛在的 ArithmeticException,它的 right 包含有效載荷,即結果。因此,為了得到實際的答案,我必須遍歷 result.right().value().right().value().doubleValue()!顯然,這種方法的實用性迅速瓦解,但它確實提供了一個類型安全的方式,將異常嵌套為類簽名的一部分。

Option 類

Either 是一個方便的概念,在下期文章中,我將使用這個概念構建樹形數據結構。Scala 中有一個名為 Option 的類與之類似,該類在 Functional Java 中被復制,提供了一個更簡單的異常情況:none 表示不合法的值,some 表示成功返回。Option 如清單 10 所示:

清單 10. 使用 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);
}

如 清單 10 所示,Option 包含 none() 或 some(),類似于 Either 中的 left 和 right,但特定于可能沒有合法返回值的方法。

Functional Java 中的 Either 和 Option 都是單體,表示計算 的特殊數據結構,在函數式語言中大量使用。在下一期中,我將探討有關 Either 的單體概念,并在不同的示例中演示它如何支持 Scala 風格的模式匹配。

關于“怎樣利用Either和Option進行函數式錯誤處理的示例”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

汶川县| 项城市| 崇礼县| 浏阳市| 康马县| 苏尼特右旗| 岱山县| 吴忠市| 嘉善县| 白山市| 林周县| 浮山县| 梓潼县| 文安县| 清远市| 静安区| 平乐县| 射洪县| 都匀市| 留坝县| 甘德县| 子洲县| 绿春县| 凤凰县| 桂阳县| 谷城县| 崇文区| 镇康县| 普格县| 江口县| 连江县| 多伦县| 娄底市| 嘉定区| 柏乡县| 泾阳县| 济宁市| 灯塔市| 朔州市| 蕲春县| 绍兴市|