functor_纯Java中的Functor和Monad示例
functor
本文最初是我們使用RxJava進(jìn)行React式編程的附錄。 但是,盡管與React式編程非常相關(guān),但對(duì)monad的介紹卻不太適合。 因此,我決定將其取出并作為博客文章單獨(dú)發(fā)布。 我知道,“ 我對(duì)單子的自己的,一半正確和一半的完整解釋 ”是編程博客上的新“ Hello,world ”。 然而,本文從Java數(shù)據(jù)結(jié)構(gòu)和庫(kù)的特定角度研究了函子和monad。 因此,我認(rèn)為值得分享。
RxJava的設(shè)計(jì)和構(gòu)建基于非常基本的概念,例如函子 , monoid和monad 。 盡管Rx最初是為命令式C#語(yǔ)言建模的,并且我們正在學(xué)習(xí)RxJava,并在類似的命令式語(yǔ)言上工作,但該庫(kù)還是源于函數(shù)式編程。 在意識(shí)到RxJava API的緊湊性之后,您應(yīng)該不會(huì)感到驚訝。 幾乎只有少數(shù)幾個(gè)核心類,通常是不可變的,并且所有內(nèi)容都主要由純函數(shù)組成。
隨著函數(shù)式編程(或函數(shù)式樣式)的最新興起(最普遍地用Scala或Clojure等現(xiàn)代語(yǔ)言表示),monads成為了廣泛討論的話題。 他們周圍有很多民間傳說(shuō):
monad是endofunctors類別中的monoid,這是什么問(wèn)題?
詹姆斯·伊里
該monad的詛咒是,一旦您獲得了頓悟,一旦您理解了“哦,就是這樣”,您就失去了向任何人解釋它的能力。
道格拉斯·克羅克福德
絕大多數(shù)程序員,尤其是那些沒(méi)有函數(shù)式編程背景的程序員,都傾向于認(rèn)為monad是某種神秘的計(jì)算機(jī)科學(xué)概念,因此從理論上講,它對(duì)他們的編程事業(yè)無(wú)濟(jì)于事。 這種消極的觀點(diǎn)可以歸因于數(shù)十篇文章或博客文章太抽象或太狹窄。 但是事實(shí)證明,甚至標(biāo)準(zhǔn)的Java庫(kù)都存在monad,特別是自Java Development Kit(JDK)8起(稍后會(huì)有更多介紹)。 絕對(duì)妙不可言的是,一旦您第一次了解monad,突然之間就會(huì)有幾個(gè)完全不相同的目的無(wú)關(guān)的類和抽象變得熟悉。
Monad概括了各種看似獨(dú)立的概念,因此學(xué)習(xí)Monad的另一種化身只需很少的時(shí)間。 例如,您不必學(xué)習(xí)CompletableFuture在Java 8中的工作方式,一旦意識(shí)到它是monad,就可以精確地知道它是如何工作的,并且可以從其語(yǔ)義中得到什么。 然后您會(huì)聽(tīng)說(shuō)RxJava聽(tīng)起來(lái)有很多不同,但是由于Observable是monad,因此沒(méi)有太多可添加的。 您已經(jīng)不知不覺(jué)中已經(jīng)遇到過(guò)許多其他的單子示例。 因此,即使您實(shí)際上沒(méi)有使用RxJava,本節(jié)也將是有用的復(fù)習(xí)。
函子
在解釋什么是monad之前,讓我們研究一個(gè)稱為functor的簡(jiǎn)單結(jié)構(gòu)。 函子是封裝某些值的類型化數(shù)據(jù)結(jié)構(gòu)。 從語(yǔ)法的角度來(lái)看,函子是具有以下API的容器:
import java.util.function.Function;interface Functor<T> {<R> Functor<R> map(Function<T, R> f);}但是僅僅語(yǔ)法是不足以了解什么是函子。 functor提供的唯一操作是帶函數(shù)f map() 。 此函數(shù)接收框內(nèi)的任何內(nèi)容,對(duì)其進(jìn)行轉(zhuǎn)換并將結(jié)果按原樣包裝到另一個(gè)函子中。 請(qǐng)仔細(xì)閱讀。 Functor<T>始終是不可變的容器,因此map不會(huì)使執(zhí)行該操作的原始對(duì)象發(fā)生突變。 取而代之的是,它返回包裝在全新函子中的結(jié)果(或結(jié)果–請(qǐng)耐心等待),該函子可能是類型R 此外,在應(yīng)用標(biāo)識(shí)函數(shù)(即map(x -> x)時(shí),函子不應(yīng)執(zhí)行任何操作。 這種模式應(yīng)始終返回相同的函子或相等的實(shí)例。
通常將Functor<T>與保存T實(shí)例進(jìn)行比較,其中與該值交互的唯一方法是對(duì)其進(jìn)行轉(zhuǎn)換。 但是,沒(méi)有從函子解開(kāi)或逃逸的慣用方式。 值始終在函子的上下文內(nèi)。 函子為什么有用? 它們使用一個(gè)統(tǒng)一的,適用于所有對(duì)象的統(tǒng)一API來(lái)概括多個(gè)通用習(xí)語(yǔ),如集合,promise,Optionals等。 讓我介紹幾個(gè)函子,以使您更流暢地使用此API:
interface Functor<T,F extends Functor<?,?>> {<R> F map(Function<T,R> f); }class Identity<T> implements Functor<T,Identity<?>> {private final T value;Identity(T value) { this.value = value; }public <R> Identity<R> map(Function<T,R> f) {final R result = f.apply(value);return new Identity<>(result);}}需要額外的F類型參數(shù)來(lái)進(jìn)行Identity編譯。 在前面的示例中,您看到的是最簡(jiǎn)單的函子,僅包含一個(gè)值。 您只能使用map方法內(nèi)部的值對(duì)其進(jìn)行轉(zhuǎn)換,但無(wú)法提取它。 這被認(rèn)為超出了純函子的范圍。 與函子進(jìn)行交互的唯一方法是應(yīng)用類型安全的轉(zhuǎn)換序列:
Identity<String> idString = new Identity<>("abc"); Identity<Integer> idInt = idString.map(String::length);或流利地,就像您編寫函數(shù)一樣:
Identity<byte[]> idBytes = new Identity<>(customer).map(Customer::getAddress).map(Address::street).map((String s) -> s.substring(0, 3)).map(String::toLowerCase).map(String::getBytes);從這個(gè)角度來(lái)看,在函子上的映射與調(diào)用鏈?zhǔn)胶瘮?shù)沒(méi)有太大不同:
byte[] bytes = customer.getAddress().street().substring(0, 3).toLowerCase().getBytes();您為什么還要煩惱這種冗長(zhǎng)的包裝,不僅不提供任何附加值,而且也無(wú)法將內(nèi)容提取回去? 好吧,事實(shí)證明您可以使用此原始函子抽象對(duì)其他幾個(gè)概念建模。 例如,從Java 8開(kāi)始的java.util.Optional<T>是帶有map()方法的函子。 讓我們從頭開(kāi)始實(shí)現(xiàn)它:
class FOptional<T> implements Functor<T,FOptional<?>> {private final T valueOrNull;private FOptional(T valueOrNull) {this.valueOrNull = valueOrNull;}public <R> FOptional<R> map(Function<T,R> f) {if (valueOrNull == null)return empty();elsereturn of(f.apply(valueOrNull));}public static <T> FOptional<T> of(T a) {return new FOptional<T>(a);}public static <T> FOptional<T> empty() {return new FOptional<T>(null);}}現(xiàn)在變得有趣了。 FOptional<T>函子可以保存一個(gè)值,但也可以為空。 這是一種對(duì)null進(jìn)行編碼的類型安全的方法。 構(gòu)造FOptional方法有兩種:通過(guò)提供值或創(chuàng)建empty()實(shí)例。 在這兩種情況下,就像Identity , FOptional是不可變的,我們只能從內(nèi)部與值交互。 FOptional不同之FOptional在于,如果轉(zhuǎn)換函數(shù)f為空,則它可能不會(huì)應(yīng)用于任何值。 這意味著函子可能不必完全封裝類型T一個(gè)值。 它也可以包裝任意數(shù)量的值,就像List …functor:
import com.google.common.collect.ImmutableList;class FList<T> implements Functor<T, FList<?>> {private final ImmutableList<T> list;FList(Iterable<T> value) {this.list = ImmutableList.copyOf(value);}@Overridepublic <R> FList<?> map(Function<T, R> f) {ArrayList<R> result = new ArrayList<R>(list.size());for (T t : list) {result.add(f.apply(t));}return new FList<>(result);} }API保持不變:在轉(zhuǎn)換T -> R使用函子,但是行為卻大不相同。 現(xiàn)在,我們對(duì)FList每個(gè)項(xiàng)目應(yīng)用轉(zhuǎn)換,以聲明方式轉(zhuǎn)換整個(gè)列表。 因此,如果您有一個(gè)customers列表,并且想要他們的街道列表,則非常簡(jiǎn)單:
import static java.util.Arrays.asList;FList<Customer> customers = new FList<>(asList(cust1, cust2));FList<String> streets = customers.map(Customer::getAddress).map(Address::street);這不再像說(shuō)customers.getAddress().street()那樣簡(jiǎn)單,您不能在一組客戶上調(diào)用getAddress() ,必須在每個(gè)單獨(dú)的客戶上調(diào)用getAddress() ,然后將其放回一個(gè)集合中。 順便說(shuō)一句,Groovy發(fā)現(xiàn)這種模式是如此普遍,以至于實(shí)際上有一個(gè)語(yǔ)法糖: customer*.getAddress()*.street() 。 該運(yùn)算符稱為散點(diǎn)圖,實(shí)際上是變相的map 。 也許您想知道為什么我要在map list手動(dòng)遍歷list而不是使用Java 8中的Stream : list.stream().map(f).collect(toList()) ? 這會(huì)響嗎? 如果我告訴您Java java.util.stream.Stream<T>也是一個(gè)函子怎么辦? 順便說(shuō)一句,一個(gè)單子?
現(xiàn)在,您應(yīng)該看到函子的第一個(gè)好處–它們抽象了內(nèi)部表示形式,并為各種數(shù)據(jù)結(jié)構(gòu)提供了一致且易于使用的API。 作為最后一個(gè)示例,讓我介紹類似于Future promise函數(shù)。 Promise “承諾”某一天將提供一個(gè)值。 它尚未出現(xiàn),可能是因?yàn)楫a(chǎn)生了一些后臺(tái)計(jì)算,或者我們正在等待外部事件。 但是它將在將來(lái)出現(xiàn)。 完成Promise<T>的機(jī)制并不有趣,但是函子的性質(zhì)是:
Promise<Customer> customer = //... Promise<byte[]> bytes = customer.map(Customer::getAddress).map(Address::street).map((String s) -> s.substring(0, 3)).map(String::toLowerCase).map(String::getBytes);看起來(lái)很熟悉? 這就是重點(diǎn)! Promise函子的實(shí)現(xiàn)超出了本文的范圍,甚至不重要。 不用說(shuō),我們非常接近從Java 8實(shí)現(xiàn)CompletableFuture ,并且?guī)缀鯊腞xJava中發(fā)現(xiàn)了Observable 。 但是回到函子。 Promise<Customer>尚未持有Customer的值。 它有望在將來(lái)具有這種價(jià)值。 但是我們?nèi)匀豢梢韵袷褂肍Optional和FList一樣映射此類函子–語(yǔ)法和語(yǔ)義完全相同。 行為遵循函子表示的內(nèi)容。 調(diào)用customer.map(Customer::getAddress)產(chǎn)生Promise<Address> ,這意味著map是非阻塞的。 customer.map() 不會(huì)等待基礎(chǔ)的customer承諾完成。 相反,它返回另一個(gè)不同類型的承諾。 當(dāng)上游承諾完成時(shí),下游承諾將應(yīng)用傳遞給map()的函數(shù)并將結(jié)果傳遞給下游。 突然,我們的函子使我們能夠以非阻塞方式流水線進(jìn)行異步計(jì)算。 但是您不必理解或?qū)W習(xí)-因?yàn)镻romise是函子,所以它必須遵循語(yǔ)法和法則。
函子還有許多其他很好的例子,例如以組合方式表示值或錯(cuò)誤。 但是現(xiàn)在是時(shí)候看看單子了。
從函子到單子
我假設(shè)您了解函子是如何工作的,為什么它們是有用的抽象。 但是函子并不像人們期望的那樣普遍。 如果您的轉(zhuǎn)換函數(shù)(作為參數(shù)傳遞給map()那個(gè))返回函子實(shí)例而不是簡(jiǎn)單值,會(huì)發(fā)生什么? 好吧,函子也只是一個(gè)值,所以沒(méi)有壞事發(fā)生。 將返回的所有內(nèi)容放回函子中,以便所有行為都保持一致。 但是,假設(shè)您有以下方便的方法來(lái)解析String :
FOptional<Integer> tryParse(String s) {try {final int i = Integer.parseInt(s);return FOptional.of(i);} catch (NumberFormatException e) {return FOptional.empty();} }例外是會(huì)破壞類型系統(tǒng)和功能純度的副作用。 在純函數(shù)語(yǔ)言中,沒(méi)有例外的地方,畢竟我們從來(lái)沒(méi)有聽(tīng)說(shuō)過(guò)在數(shù)學(xué)課上拋出例外,對(duì)嗎? 錯(cuò)誤和非法條件使用值和包裝器明確表示。 例如, tryParse()接受一個(gè)String但并不簡(jiǎn)單地返回int或在運(yùn)行時(shí)靜默引發(fā)異常。 通過(guò)類型系統(tǒng),我們明確告知tryParse()可能失敗,字符串格式錯(cuò)誤不會(huì)有任何異常或錯(cuò)誤。 此半故障由可選結(jié)果表示。 有趣的是,Java已經(jīng)檢查了必須聲明和處理的異常,因此從某種意義上講,Java在這方面比較純凈,它沒(méi)有隱藏副作用。 但是,無(wú)論好壞,通常在Java中不建議使用檢查異常,因此讓我們回到tryParse() 。 用已經(jīng)包裝在FOptional String組成tryParse似乎很有用:
FOptional<String> str = FOptional.of("42"); FOptional<FOptional<Integer>> num = str.map(this::tryParse);這不足為奇。 如果tryParse()返回一個(gè)int您將得到FOptional<Integer> num ,但是由于map()函數(shù)返回FOptional<Integer>本身,因此它被包裝兩次,成為笨拙的FOptional<FOptional<Integer>> 。 請(qǐng)仔細(xì)查看類型,您必須了解為什么在這里獲得此雙重包裝。 除了看上去很恐怖之外,在函子中放一個(gè)函子會(huì)破壞構(gòu)圖和流暢的鏈接:
FOptional<Integer> num1 = //... FOptional<FOptional<Integer>> num2 = //...FOptional<Date> date1 = num1.map(t -> new Date(t));//doesn't compile! FOptional<Date> date2 = num2.map(t -> new Date(t));在這里,我們嘗試通過(guò)將int轉(zhuǎn)換為+ Date +來(lái)映射FOptional的內(nèi)容。 具有Functor<Integer> int -> Date的功能,我們可以輕松地從Functor<Integer>為Functor<Date> ,我們知道它是如何工作的。 但是在num2情況下情況變得復(fù)雜。 num2.map()接收的輸入不再是int而是FOoption<Integer> ,顯然java.util.Date沒(méi)有這樣的構(gòu)造函數(shù)。 我們通過(guò)雙重包裹來(lái)破壞了函子。 但是,具有返回函子而不是簡(jiǎn)單值的函數(shù)非常普遍(例如tryParse() ),因此我們不能簡(jiǎn)單地忽略這種要求。 一種方法是引入一種特殊的無(wú)參數(shù)join()方法,以“展平”嵌套函子:
FOptional<Integer> num3 = num2.join()它可以工作,但是因?yàn)檫@種模式太普遍了,所以引入了一種名為flatMap()特殊方法。 flatMap()與map非常相似,但希望作為參數(shù)接收的函數(shù)返回函子-或monad是精確的:
interface Monad<T,M extends Monad<?,?>> extends Functor<T,M> {M flatMap(Function<T,M> f); }我們僅得出結(jié)論, flatMap只是一種語(yǔ)法糖,可以實(shí)現(xiàn)更好的組合。 但是flatMap方法(通常稱為Haskell的bind或>>= )具有所有不同之處,因?yàn)樗试S以純函數(shù)式的形式構(gòu)成復(fù)雜的轉(zhuǎn)換。 如果FOptional是monad的實(shí)例,則解析突然可以按預(yù)期進(jìn)行:
FOptional<Integer> num = FOptional.of(42); FOptional<Integer> answer = num.flatMap(this::tryParse);Monads不需要實(shí)現(xiàn)map ,可以輕松地在flatMap()之上實(shí)現(xiàn)它。 實(shí)際上, flatMap是啟用全新轉(zhuǎn)換領(lǐng)域的基本運(yùn)算符。 顯然,就像函子一樣,語(yǔ)法順從性還不足以將某個(gè)類稱為monad, flatMap()運(yùn)算符必須遵循monad定律,但它們非常直觀,就像flatMap()與標(biāo)識(shí)的關(guān)聯(lián)性一樣。 后者要求m(x).flatMap(f)與持有值x任何monad和函數(shù)f f(x)相同。 我們不會(huì)深入研究monad理論,而讓我們關(guān)注實(shí)際含義。 當(dāng)內(nèi)部結(jié)構(gòu)并非無(wú)關(guān)緊要時(shí),Monad便會(huì)發(fā)光,例如Promise monad,它將在將來(lái)具有一定的價(jià)值。 您可以從類型系統(tǒng)中猜測(cè)Promise在以下程序中的表現(xiàn)嗎? 首先,所有可能花費(fèi)一些時(shí)間才能完成的方法都會(huì)返回Promise :
import java.time.DayOfWeek;Promise<Customer> loadCustomer(int id) {//... }Promise<Basket> readBasket(Customer customer) {//... }Promise<BigDecimal> calculateDiscount(Basket basket, DayOfWeek dow) {//... }現(xiàn)在,我們可以將這些函數(shù)組合起來(lái),就好像它們都是使用單子運(yùn)算符進(jìn)行了阻塞一樣:
Promise<BigDecimal> discount = loadCustomer(42).flatMap(this::readBasket).flatMap(b -> calculateDiscount(b, DayOfWeek.FRIDAY));這變得很有趣。 flatMap()必須保留monadic類型,因?yàn)樗兄虚g對(duì)象都是Promise 。 不僅僅是保持類型有序-先前的程序突然完全異步! loadCustomer()返回Promise因此不會(huì)阻塞。 readBasket()接受Promise擁有(將要擁有的一切)并應(yīng)用返回另一個(gè)Promise的函數(shù),依此類推。 基本上,我們建立了一個(gè)異步計(jì)算管道,其中后臺(tái)完成一個(gè)步驟會(huì)自動(dòng)觸發(fā)下一步。
探索
有兩個(gè)單子并將它們包含的值組合在一起是很常見(jiàn)的。 但是函子和monad均不允許直接訪問(wèn)其內(nèi)部,這是不純的。 相反,我們必須謹(jǐn)慎地應(yīng)用轉(zhuǎn)換,而不能逃脫monad。 假設(shè)您有兩個(gè)單子,并且您想將它們合并
import java.time.LocalDate; import java.time.Month;Monad<Month> month = //... Monad<Integer> dayOfMonth = //...Monad<LocalDate> date = month.flatMap((Month m) ->dayOfMonth.map((int d) -> LocalDate.of(2016, m, d)));請(qǐng)花點(diǎn)時(shí)間研究前面的偽代碼。 我沒(méi)有使用任何真正的monad實(shí)現(xiàn)(例如Promise或List來(lái)強(qiáng)調(diào)核心概念。 我們有兩個(gè)獨(dú)立的monad,一個(gè)是Month類型,另一個(gè)是Integer類型。 為了從中構(gòu)建LocalDate ,我們必須構(gòu)建一個(gè)嵌套的轉(zhuǎn)換,該轉(zhuǎn)換可以訪問(wèn)兩個(gè)monad的內(nèi)部。 仔細(xì)研究這些類型,尤其要確保您了解為什么我們?cè)谝粋€(gè)地方使用flatMap在另一個(gè)地方使用map() 。 想想如果您還有第三個(gè)Monad<Year> ,那么您將如何構(gòu)造此代碼。 這種應(yīng)用兩個(gè)參數(shù)(在本例中為m和d )的函數(shù)的模式非常普遍,以至于Haskell中有一個(gè)特殊的輔助函數(shù),稱為liftM2 ,它完全在map和flatMap上實(shí)現(xiàn)了這種轉(zhuǎn)換。 在Java偽語(yǔ)法中,它看起來(lái)像這樣:
Monad<R> liftM2(Monad<T1> t1, Monad<T2> t2, BiFunction<T1, T2, R> fun) {return t1.flatMap((T1 tv1) ->t2.map((T2 tv2) -> fun.apply(tv1, tv2))); }您不必為每個(gè)monad實(shí)現(xiàn)此方法, flatMap()足夠了,而且它對(duì)所有monad都一致地起作用。 當(dāng)您考慮如何將其與各種monad一起使用時(shí), liftM2非常有用。 例如listM2(list1, list2, function)將對(duì)list1和list2 (笛卡爾積)中的每對(duì)可能的項(xiàng)應(yīng)用function 。 另一方面,對(duì)于可選選項(xiàng),僅當(dāng)兩個(gè)可選選項(xiàng)均為非空時(shí),它將應(yīng)用功能。 更好的是,對(duì)于Promise monad,當(dāng)兩個(gè)Promise都完成時(shí),將異步執(zhí)行一個(gè)函數(shù)。 這意味著我們只是發(fā)明了一個(gè)簡(jiǎn)單的同步機(jī)制(分叉聯(lián)接算法中的join() ,該同步機(jī)制包含兩個(gè)異步步驟。
我們可以輕松地在flatMap()之上構(gòu)建的另一個(gè)有用的運(yùn)算符是filter(Predicate<T>) ,該運(yùn)算符接收monad內(nèi)部的所有內(nèi)容,如果不符合某些謂詞,則將其完全丟棄。 在某種程度上,它類似于map但不是1-to-1映射,而是1-to-0-or-1。 同樣, filter()對(duì)于每個(gè)monad具有相同的語(yǔ)義,但是取決于我們實(shí)際使用的monad,其功能相當(dāng)驚人。 顯然,它允許從列表中過(guò)濾掉某些元素:
FList<Customer> vips = customers.filter(c -> c.totalOrders > 1_000);但是它也可以正常工作,例如對(duì)于可選項(xiàng)目。 在這種情況下,如果可選內(nèi)容不符合某些條件,我們可以將非空可選內(nèi)容轉(zhuǎn)換為空值。 空的可選部分保持不變。
從單子列表到單子列表
另一個(gè)來(lái)自flatMap()有用運(yùn)算符是sequence() 。 您只需查看類型簽名即可輕松猜測(cè)其作用:
Monad<Iterable<T>> sequence(Iterable<Monad<T>> moands)通常,我們有一堆相同類型的monad,而我們想要一個(gè)具有該類型列表的monad。 對(duì)您來(lái)說(shuō),這聽(tīng)起來(lái)似乎很抽象,但卻非常有用。 想象一下,您想通過(guò)ID同時(shí)從數(shù)據(jù)庫(kù)中加載一些客戶,因此您多次對(duì)不同的ID使用loadCustomer(id)方法,每次調(diào)用都返回Promise<Customer> 。 現(xiàn)在,您有了Promise的列表,但您真正想要的是客戶列表,例如要在Web瀏覽器中顯示的客戶列表。 sequence() (在RxJava中sequence()稱為concat()或merge() ,具體取決于用例)是為此目的而構(gòu)建的:
FList<Promise<Customer>> custPromises = FList.of(1, 2, 3).map(database::loadCustomer);Promise<FList<Customer>> customers = custPromises.sequence();customers.map((FList<Customer> c) -> ...);通過(guò)為每個(gè)ID調(diào)用database.loadCustomer(id) ,我們可以在其上map一個(gè)表示客戶ID的FList<Integer> (您看到FList是一個(gè)函子嗎?) 這導(dǎo)致Promise列表非常不便。 sequence()節(jié)省了一天的時(shí)間,但是再次這不僅是語(yǔ)法糖。 前面的代碼是完全非阻塞的。 對(duì)于不同種類的monads, sequence()仍然有意義,但是在不同的計(jì)算上下文中。 例如,可以將FList<FOptional<T>>更改為FOptional<FList<T>> 。 順便說(shuō)一句,您可以在flatMap()之上實(shí)現(xiàn)sequence() (就像map()一樣flatMap() 。
一般而言,這只是關(guān)于flatMap()和monad有用性的冰山一角。 盡管源于晦澀的類別理論,但即使在Java之類的面向?qū)ο蟮木幊陶Z(yǔ)言中,monad也被證明是極其有用的抽象。 能夠組成返回單子函數(shù)的函數(shù)非常有用,以至于數(shù)十個(gè)無(wú)關(guān)的類遵循單子行為。
而且,一旦將數(shù)據(jù)封裝在monad中,通常很難顯式地將其取出。 這種操作不是monad行為的一部分,并且經(jīng)常導(dǎo)致非慣用語(yǔ)代碼。 例如, Promise<T>上的Promise.get()可以從技術(shù)上返回T ,但是只能通過(guò)阻塞來(lái)返回,而所有基于flatMap()運(yùn)算符都是非阻塞的。 另一個(gè)示例是FOptional.get()可能會(huì)失敗,因?yàn)镕Optional可能為空。 即使FList.get(idx)從列表偷窺特定元素聽(tīng)起來(lái)很別扭,因?yàn)槟憧梢蕴鎿Qfor與循環(huán)map()經(jīng)常。
我希望您現(xiàn)在了解為什么單子如此流行。 即使在像Java這樣的面向?qū)ο蟮恼Z(yǔ)言中,它們也是非常有用的抽象。
翻譯自: https://www.javacodegeeks.com/2016/06/functor-monad-examples-plain-java.html
functor
總結(jié)
以上是生活随笔為你收集整理的functor_纯Java中的Functor和Monad示例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 福建房产备案信息查询(福建房产备案)
- 下一篇: spring 自定义日志_Spring和