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