monad 是一個源自數學的一部分的概念,稱為 范疇論,而不是類或特征。在本文中,我將嘗試解釋它的結構和內部工作原理。通過使用 JAVA 中的Optional ,我將嘗試以更易于理解的方式描述所有這些。我還將實現一個基本的 monad 以更好地理解它們的工作原理,并以一個簡短的使用示例作為結尾,以展示 monad 相對于非 monad 方法的優勢。
為什么要學習 Monads 是如何工作的?
首先,對我們使用的東西如何工作有一個基本的了解總是好的。如果您是 Java 開發人員,您可能會使用 monad,甚至可能不知道。這可能會讓您感到驚訝,但Java 8 最著名的兩個特性,即Stream和Optional是 monad 實現。
讓我們從描述什么是 monad 開始——或多或少準確。在我看來,這里的問題相當簡單。
Monad 只是內函子范疇中的一個幺半群
基于 Saunders mac Lane 的 “工作數學家分類”中的 一句話 。
回到認真...
什么是單子?
閱讀介紹后,您知道 monad 是范疇論中的一個概念。在軟件世界中,它可以在任何具有泛型支持的靜態類型語言中實現為類或特征。此外,我們可以將其視為一個包裝器,將我們的值放在某個上下文中,并允許我們對值執行操作,特別是返回包裝在同一上下文中的值的操作。此外,我們可以以這樣一種方式鏈接操作,即任何步驟的操作的輸出都是下一步操作的輸入。
現代編程語言中的單子示例:
流(Java)。
可選/選項(Java/Scala)。
要么(斯卡拉)。
嘗試(規模)。
IO Monad(哈斯克爾)。
單子定律
在談到 monad 時,最后需要提及的是它們的法則。如果我們想將我們的實現視為一個真正的 monad,我們必須服從它們。存在三個定律:左身份、右身份和關聯性。在我看來,理解它們的實際含義可能有些困難。
現在,在Optional的幫助下,我將嘗試更詳細地解釋上述規律。
但首先有幾個假設:
f 是從類型 T 到類型 Optional<R> 的函數映射
g 是從類型 R 到類型 Optional<U> 的函數映射
1.左身份
如果我們創建一個新的 monad 并將其綁定到函數,結果應該與將函數應用于值相同。
Optional<String> leftIdentity = Optional.of(x).flatMap(f);Optional<String> mAppedX = f.apply(x);assert leftIdentity.equals(mappedX);
2.正確的身份
將單元函數綁定到 monad 的結果應該與創建新 monad 的結果相同。
Optional<Integer> rightIdentity = Optional.of(x).flatMap(Optional::of);Optional<Integer> wrappedX = Optional.of(x);assert rightIdentity.equals(wrappedX);
3.關聯性
在函數應用程序鏈中,函數如何嵌套并不重要。
Optional<Long> leftSide = Optional.of(x).flatMap(f).flatMap(g);Optional<Long> rightSide = Optional.of(x).flatMap(v -> f.apply(v).flatMap(g));assert leftSide.equals(rightSide);
如果你喜歡閱讀 Monads 并想學習類似的相關概念,關注與私信博主
https://docs.qq.com/doc/DQ2Z0eE1aUmlITnNz
單子的創造
現在,當我們了解了基礎知識后,我們就可以專注于實施了。
我們需要的第一件事是參數化類型 M<T>,它是類型 T 的值的包裝器。我們的類型必須實現兩個函數:
of ( unit ) 用于包裝我們的值并具有以下簽名M<T>(T)。
flatMap ( bind ) 負責執行操作。在這里,我們傳遞了一個函數,該函數對上下文中的值進行操作,并以已經包裝在上下文中的另一種類型返回它。此方法應具有以下簽名M<U> (T -> M<U>)。
為了更容易理解,我將再使用一次Optional并展示上面的結構在這種情況下的樣子。
在這里,第一個條件立即得到滿足,因為Optional是參數化類型。單位函數的作用由ofNullable和of方法完成。FlatMap 起到綁定功能的作用。當然,在Optional的情況下,類型邊界允許我們使用比上面定義更復雜的類型。
理論講完了,我們來實現
import java.util.function.Function; public final class WrapperMonad<T> { private final T value; private WrapperMonad(T value) { this.value = value; } static <T> WrapperMonad<T> of(T value) { return new WrapperMonad<>(value); } <U> WrapperMonad<U> flatMap(Function<T, WrapperMonad<U>> function) { return function.apply(value); } // For sake of asserting in Example boolean valueEquals(T x) { return value.equals(x); } }
等等,monad 實現了。讓我們詳細描述一下我在這里做了什么。
這里到底發生了什么
我們實現的基礎是具有名為“value”的不可變字段的參數化類,它負責存儲我們的值。然后,我們有一個私有構造函數,這使得除了通過我們的包裝方法 - of之外的任何其他方式都無法創建對象。
接下來,我們有兩個基本的 monad 函數,即of(等價于unit)和flatMap(等價于bind),這將保證我們的實現以 monad 法則的形式滿足所需條件。
有了所描述的功能,現在是使用示例的時候了。所以就在這里。
import java.util.function.Function; public class Example { public static void main(String[] args) { int x = 2; // Task: performing operation, returning wrapped value, over the value inside the container object. // Non-Monad Function<Integer, Wrapper<String>> toString = i -> new Wrapper<>(i.toString()); Function<String, Wrapper<Integer>> hashCode = str -> new Wrapper<>(str.hashCode()); Wrapper<Integer> wrapper = new Wrapper<>(x); Wrapper<String> stringifyWrapper = toString.apply(wrapper.value); // One liner - Wrapper<Integer> hashCodedWrapper = hashCode.apply(toString.apply(x).value); Wrapper<Integer> hashCodedWrapper = hashCode.apply(stringifyWrapper.value); // Monad Function<Integer, WrapperMonad<String>> toStringM = i -> WrapperMonad.of(i.toString()); Function<String, WrapperMonad<Integer>> hashCodeM = str -> WrapperMonad.of(str.hashCode()); WrapperMonad<Integer> hashCodedWrapperMonadic = WrapperMonad.of(x) .flatMap(toStringM) .flatMap(hashCodeM); assert hashCodedWrapperMonadic.valueEquals(hashCodedWrapper.value); System.out.println("Values inside wrappers are equal"); } }
在上面的代碼中,除了看到 monad 是如何工作的,我們還可以看到使用它們的一些優點。
在 monadic 部分,所有操作都組合到一個執行管道中,這使得代碼更具聲明性,更易于閱讀和理解。此外,如果有一天我們決定添加錯誤處理邏輯,它可以很好地封裝在flatMap方法中。
另一方面,在示例的非單子部分中,我們使用包私有字段值進行了不同的設計,我們需要一種從包裝器外部訪問值的方法,這破壞了封裝。就目前而言,該片段足夠可讀,但您可能會注意到它對擴展不友好。添加任何類型的異常處理都可能使它變得非常意大利面條。
加起來
Monad是一個非常有用且強大的概念,我們中的許多人可能在日常工作中使用它。我試圖對其背后的理論基礎和邏輯提供清晰和描述性的解釋。我實現了一個自定義 monad 以表明它不是一個復雜的結構。在上面的示例中,我展示了 monad 的用法,這種方法的潛在優點是什么,以及它與普通方法調用有何不同。感謝您的時間。
如果您喜歡閱讀有關 Monads 的內容并想了解更多有關其他類似概念的信息,關注與私信博主
https://docs.qq.com/doc/DQ2Z0eE1aUmlITnNz免費學習領取JAVA 課件,源碼,安裝包等資料
關于 Monad 的常見問題
什么是單子?
Monad 是一個概念,起源于稱為范疇論的數學部分。
我為什么要關心 Monad?
如果您是 Java 開發人員,您可能每天都在使用 monad,但我可以告訴您有問題。例如,Stream 和 Optional 是 monad 的實現,是最容易混淆的對象。此外,函數式編程變得越來越流行,因此我們可能會期待更多這樣的結構。
什么是單子定律?
每個 Monad 實現都必須滿足三個定律:左身份、右身份和關聯性。
我需要什么來實現 Monad?
要實現 Monad,您需要一個參數化類型 M<T> 和兩個方法 unit 和 bind。