通过Java 8中的Applicative Builder组合多个异步结果
幾個月前,我發(fā)布了一個出版物 ,在其中詳細解釋了我提出的名為Outcome的抽象,它通過強制使用語義幫助了我很多 沒有副作用的代碼。 通過遵循這種簡單(但功能強大)的約定,我最終將任何類型的故障(又稱Exception)都轉(zhuǎn)化為函數(shù)的顯式結(jié)果,從而使一切都更容易推論。 我不認識您,但是我厭倦了處理會破壞一切的異常,因此我做了一些事情,老實說,它確實工作得很好。 因此,在繼續(xù)講故事之前,我真的建議您仔細閱讀該文章。 現(xiàn)在,讓我們使用古怪的應(yīng)用思想解決一些異步問題,對吧?
這樣邪惡的事情來了
生活真是太好了,我們的編碼像以往一樣快速,干凈和可組合,但是,出乎意料的是,我們偶然發(fā)現(xiàn)了一個“缺失”功能(請大笑一下):我們需要將幾個異步 結(jié)果實例合并到一個阻礙時尚…。
受到這個想法的鼓舞,我開始工作。 我嘗試了很多時間,以尋求一種健壯而又簡單的方式來表達這種情況。 雖然新的ComposableFuture API原來比我期望的要好得多(盡管我仍然不明白為什么他們決定使用諸如applyAsync或thenComposeAsync之類的名稱,而不是map或flatMap ),但我總是以實現(xiàn)過于冗長和重復(fù)比較的方式結(jié)束我在Scala上所做的一些事情,但是經(jīng)過很長的“ Mate ”課程之后,我有了“嘿! 一刻”:為什么不使用類似于應(yīng)用程序的內(nèi)容 ?
問題
假設(shè)我們有兩個異步結(jié)果:
CompletableFuture<Outcome<String>> textf = completedFuture(maybe("And the number is %s!"));CompletableFuture<Outcome<Integer>> numberf = completedFuture(maybe(22));還有一個愚蠢的實體Message:
public static class Message{private final String _text;private final Integer _number;private Message(String msg, Integer number){_text = msg;_number = number;}public String getContent(){return String.format(_text,_number);} }我需要給定textf和numberf的東西,它會給我類似的東西
//After combining textf and numberf CompletableFuture<Outcome<Message>> message = ....所以我給圣誕老人寫了一封信:
適用于救援人員
如果您考慮一下,那么一種簡單的方法來表達我們想要實現(xiàn)的目標(biāo)如下:
// Given a String -> Given a number -> Format the message f: String -> Integer -> Message檢查f的定義,它表示類似:“給出一個String ,我將返回一個以Integer作為參數(shù)的函數(shù),該函數(shù)在應(yīng)用時將以這種方式返回Message類型的實例,而不是等待所有值一次可用,我們可以一次部分應(yīng)用一個值,以獲得對Message實例構(gòu)造過程的實際描述。 聽起來不錯。
要實現(xiàn)這一點,如果我們可以使用構(gòu)造lambda Message:new并對其進行咖喱 ,再說一次,boom!,done !,將是非常了不起的,但是在Java中這是不可能的(以一種通用,美觀和簡潔的方式進行),因此對于為了我們的示例,我決定采用我們鐘愛的Builder模式,該模式確實可以勝任:
public static class Builder implements WannabeApplicative<Message> {private String _text;private Integer _number;public Builder text(String text){_text=text;return this;}public Builder number(Integer number){_number=number;return this;}@Overridepublic Message apply() {return new Message(_text,_number);} }這是WannabeApplicative <T>的定義:
public interface WannabeApplicative<V> {V apply(); }Disclamer : 對于那些功能 怪癖 ,這本身并不是適用的,我知道,但是我從中汲取了一些想法,并根據(jù)語言提供給我的工具對其進行了調(diào)整。 因此,如果您感到好奇,請查看此帖子以獲取更正式的示例。
如果您仍在我身邊,我們可以同意,到目前為止,我們并沒有做過任何復(fù)雜的事情,但是現(xiàn)在我們需要表達一個構(gòu)建步驟,記住,該步驟必須是無阻塞的,并且能夠合并以前發(fā)生的任何故障可能發(fā)生在其他可能有新執(zhí)行的處決中。 因此,為了做到這一點,我提出了以下內(nèi)容:
public static class CompositionSources<B> {private CompositionSources(){ }public interface Partial<B>{CompletableFuture<Outcome<B>> apply(CompletableFuture<Outcome<B>> b);}public interface MergingStage<B, V>{Partial<B> by(BiFunction<Outcome<B>, Outcome<V>, Outcome<B>> f);}public <V> MergingStage<B, V> value(CompletableFuture<Outcome<V>> value){return f -> builder-> builder.thenCombine(value, (b, v) -> f.apply(b, v).dependingOn(b).dependingOn(v));}public static <B> CompositionSources<B> stickedTo(Class<B> clazz){return new CompositionSources<>();} }首先,我們有兩個功能接口:一個是Partial <B> ,它表示將值懶惰地應(yīng)用于生成器 ,而第二個接口MergingStage <B,V>表示“如何”組合既是建設(shè)者又是價值 。 然后,我們得到了一個名為value的方法,給定類型為CompletableFuture <Outcome <V >>的實例,它將返回類型為MergingStage <B,V>的實例,并且信不信由你,這就是發(fā)生魔術(shù)的地方。 如果您還記得MergingState定義, 則將看到它是BiFunction ,其中第一個參數(shù)的類型為Outcome <B> ,第二個參數(shù)的類型為Outcome <V> 。 現(xiàn)在,如果遵循這些類型,則可以說出兩件事:一側(cè)的構(gòu)建過程的部分狀態(tài)(類型參數(shù)B)和需要應(yīng)用于構(gòu)建器當(dāng)前狀態(tài)的新值(類型參數(shù)V),以便在應(yīng)用時將生成一個新的構(gòu)建器實例,該實例具有“構(gòu)建順序中的下一個狀態(tài)”,由Partial <B>表示 。 最后但并非最不重要的一點,我們有stickedTo方法,它基本上是一個(糟糕的Java)技巧,可在定義構(gòu)建步驟時堅持使用特定的應(yīng)用程序類型(構(gòu)建器)。 例如,具有:
CompositionSources<Builder> sources = CompositionSources.stickedTo(Builder.class);我可以為任何Builder實例定義部分值應(yīng)用程序,如下所示:
//What we're gonna do with the async text when available Partial<Builder> textToApply = sources.value(textf).by((builder, text) -> builder.flatMapR(b -> text.mapR(b::text)));//Same thing for the number Partial<Builder> numberToApply = sources.value(numberf).by((builder, number) -> builder.flatMapR(b -> number.mapR(b::number)));看到我們還沒有建立任何東西,我們只是描述了當(dāng)時間到時我們想對每個值進行的操作 ,我們可能想在使用新值之前進行一些驗證(這是在結(jié)果發(fā)揮重要作用時),或者只是使用確實如此,這完全取決于我們,但主要要點是我們還沒有應(yīng)用任何東西。 為了這樣做,并最終收緊所有松散的末端,我想出了其他一些定義,如下所示:
public static class FutureCompositions<V , A extends WannabeApplicative<V>>{private final Supplier<CompletableFuture<Outcome<A>>> _partial;private FutureCompositions(Supplier<CompletableFuture<Outcome<A>>> state){_partial=state;}public FutureCompositions<V, A> binding(Partial<A> stage){return new FutureCompositions<>(() -> stage.apply(_partial.get()));}public CompletableFuture<Outcome<V>> perform(){return _partial.get().thenApply(p -> p.mapR(WannabeApplicative::apply));}public static <V, A extends WannabeApplicative<V>> FutureCompositions<V, A> begin(A applicative){return new FutureCompositions<>(() -> completedFuture(maybe(applicative)));} }希望這不是那么令人頭疼,但我會盡力將其分解得盡可能清楚。 為了開始指定如何將整個事物組合在一起,您將從調(diào)用WannabeApplicative <V>類型的實例開始 ,在我們的示例中,它的類型參數(shù)V等于Builder 。
FutureCompositions<Message, Builder> ab = begin(Message.applicative())看到,在調(diào)用begin之后 ,您將獲得一個FutureCompositions的新實例,其中包含一個延遲評估的部分狀態(tài) ,使其成為整個構(gòu)建過程狀態(tài)的唯一所有者,而這正是我們所做的一切的最終目標(biāo)到目前為止,我們已經(jīng)做好了充分的控制,以完全控制何時以及如何將事物組合在一起。 接下來,我們必須指定要合并的值,這就是綁定方法的用途:
ab.binding(textToApply).binding(numberToApply);這樣,我們便可以使用先前定義的Partial實例為構(gòu)建器實例提供所有需要合并的值,以及每個實例應(yīng)該發(fā)生的情況的規(guī)范。 還可以看到,所有內(nèi)容仍在惰性評估中,什么都沒有發(fā)生,但是我們?nèi)匀欢询B了所有“步驟”,直到最終確定實現(xiàn)結(jié)果為止,這將在您調(diào)用perform時發(fā)生。
CompletableFuture<Outcome<Message>> message = ab.perform();從那一刻起,一切都會展開,每個構(gòu)建階段都將得到評估,可以在結(jié)果實例中返回并收集故障,或者簡單地將新可用的值以一種或另一種方式提供給目標(biāo)構(gòu)建器實例,所有步驟將被執(zhí)行直到什么都不做。 我將嘗試描述如下情況
如果您留意圖片的左側(cè),您可以輕松地看到我之前顯示的每個步驟是如何“定義的”,遵循先前的“聲明”箭頭方向,即您實際描述構(gòu)建過程的方式。 現(xiàn)在,從您調(diào)用perform的那一刻起,每個應(yīng)用實例(在我們的情況下請記住Builder )將被反方向延遲求值:它將首先求值堆棧中的最后一個指定階段,然后再求值下一個階段依次類推,直到達到建筑物定義的“起點”,它將開始展開或向上逐步展開評估,并使用MergingStage規(guī)范收集所有可能的信息 。
而這僅僅是個開始…。
我敢肯定,可以做很多事情來改善這個想法,例如:
- 連續(xù)兩次調(diào)用CompositionSources.values()的 dependingOn 很爛 ,我覺得這太冗長了,我必須對此做些事情。
- 我不太確定要繼續(xù)將結(jié)果實例傳遞給MergingStage ,如果在調(diào)用它之前解開要合并的值并只返回Either <Failure,V> ,它將看起來更干凈,更輕松-這將降低復(fù)雜性并提高靈活性關(guān)于幕后發(fā)生的事情。
- 盡管使用Builder模式完成了這項工作,但是感覺還是很老套了 ,我很想輕松地構(gòu)造函數(shù),因此在我的待辦事項清單中是檢查jOOλ或Javaslang是否可以提供某些服務(wù)。
- 更好的類型推斷,以便從代碼中消除任何不必要的干擾,例如, stickedTo方法,它的確是代碼的味道,我一開始就討厭。 絕對需要更多時間來找出從定義本身推斷應(yīng)用類型的另一種方法。
非常歡迎您給我發(fā)送您可能有的任何建議和意見。 干杯,記住……
翻譯自: https://www.javacodegeeks.com/2015/12/composing-multiple-async-results-via-applicative-builder-java-8.html
總結(jié)
以上是生活随笔為你收集整理的通过Java 8中的Applicative Builder组合多个异步结果的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: osgi 模块化_OSGI –模块化您的
- 下一篇: 设计模式的Java 8 Lambda表达