日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

java 函数式编程 示例_功能Java示例 第8部分–更多纯函数

發(fā)布時(shí)間:2023/12/3 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 函数式编程 示例_功能Java示例 第8部分–更多纯函数 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

java 函數(shù)式編程 示例

這是第8部分,該系列的最后一部分稱為“示例功能Java”。

我在本系列的每個(gè)部分中開(kāi)發(fā)的示例是某種“提要處理程序”,用于處理文檔。 在上一期文章中,我們已經(jīng)使用Vavr庫(kù)看到了一些模式匹配,并且還將故障也視為數(shù)據(jù) ,例如,采用了替代路徑并返回到功能流程。

在本系列的最后一篇文章中,我將功能發(fā)揮到了極致 :一切都變成了功能。

如果您是第一次來(lái),最好是從頭開(kāi)始閱讀。 它有助于了解我們從何處開(kāi)始以及如何在整個(gè)系列中繼續(xù)前進(jìn)。

這些都是這些部分:

  • 第1部分–從命令式到聲明式
  • 第2部分–講故事
  • 第3部分–不要使用異常來(lái)控制流程
  • 第4部分–首選不變性
  • 第5部分–將I / O移到外部
  • 第6部分–用作參數(shù)
  • 第7部分–將失敗也視為數(shù)據(jù)
  • 第8部分–更多純函數(shù)

我將在每篇文章發(fā)表時(shí)更新鏈接。 如果您通過(guò)內(nèi)容聯(lián)合組織來(lái)閱讀本文,請(qǐng)查看我博客上的原始文章。

每次代碼也被推送到這個(gè)GitHub項(xiàng)目 。

最大化運(yùn)動(dòng)部件

您可能已經(jīng)聽(tīng)過(guò)Micheal Feathers的以下短語(yǔ):

OO通過(guò)封裝運(yùn)動(dòng)部件使代碼易于理解。 FP通過(guò)最大程度地減少運(yùn)動(dòng)部件來(lái)使代碼易于理解。

好的,讓我們稍稍忘記上一期中的故障恢復(fù),然后繼續(xù)下面的版本:

FeedHandler { class FeedHandler { List<Doc> handle(List<Doc> changes, Function<Doc, Try<Resource>> creator) { changes .findAll { doc -> isImportant(doc) } .collect { doc -> creator.apply(doc) }.map { resource -> setToProcessed(doc, resource) }.getOrElseGet { e -> setToFailed(doc, e) } } } private static boolean isImportant(doc) { doc.type == 'important' } private static Doc setToProcessed(doc, resource) { doc.copyWith( status: 'processed' , apiId: resource.id ) } private static Doc setToFailed(doc, e) { doc.copyWith( status: 'failed' , error: e.message ) } }

替換為功能類(lèi)型

我們可以使用對(duì)函數(shù)接口類(lèi)型的變量(例如Predicate或BiFunction的引用來(lái)替換每種方法。

A)我們可以替換一個(gè)接受1個(gè)參數(shù)的方法,該方法返回一個(gè)布爾值

private static boolean isImportant(doc) { doc.type == 'important' }

謂詞

private static Predicate<Doc> isImportant = { doc -> doc.type == 'important' }

B),我們可以替換一個(gè)接受2個(gè)參數(shù)并返回結(jié)果的方法

private static Doc setToProcessed(doc, resource) { ... } private static Doc setToFailed(doc, e) { ... }

具有BiFunction

private static BiFunction<Doc, Resource, Doc> setToProcessed = { doc, resource -> ... } private static BiFunction<Doc, Throwable, Doc> setToFailed = { doc, e -> ... }

為了實(shí)際調(diào)用封裝在(Bi)Function中的邏輯,我們必須對(duì)其調(diào)用apply 。 結(jié)果如下:

FeedHandler { class FeedHandler { List<Doc> handle(List<Doc> changes, Function<Doc, Try<Resource>> creator) { changes .findAll { isImportant } .collect { doc -> creator.apply(doc) .map { resource -> setToProcessed.apply(doc, resource) }.getOrElseGet { e -> setToFailed.apply(doc, e) } } } private static Predicate<Doc> isImportant = { doc -> doc.type == 'important' } private static BiFunction<Doc, Resource, Doc> setToProcessed = { doc, resource -> doc.copyWith( status: 'processed' , apiId: resource.id ) } private static BiFunction<Doc, Throwable, Doc> setToFailed = { doc, e -> doc.copyWith( status: 'failed' , error: e.message ) } }

將所有輸入移至功能本身

我們將所有內(nèi)容移至方法簽名,以便FeedHandler的handle方法的調(diào)用者可以提供自己的那些功能的實(shí)現(xiàn)。

方法簽名將更改為:

List<Doc> handle(List<Doc> changes, Function<Doc, Try<Resource>> creator)

List<Doc> handle(List<Doc> changes, Function<Doc, Try<Resource>> creator, Predicate<Doc> filter, BiFunction<Doc, Resource, Doc> successMapper, BiFunction<Doc, Throwable, Doc> failureMapper)

其次,我們將重命名原始(靜態(tài)) 謂詞BiFunction變量

  • isImportant
  • setToProcessed
  • setToFailed

轉(zhuǎn)換為類(lèi)頂部的新常量 ,反映它們的新作用。

  • DEFAULT_FILTER
  • DEFAULT_SUCCESS_MAPPER
  • DEFAULT_FAILURE_MAPPER

客戶端可以完全控制是否將默認(rèn)實(shí)現(xiàn)用于某些功能,或者何時(shí)需要接管自定義邏輯。

例如,當(dāng)僅需要定制故障處理時(shí),可以這樣調(diào)用handle方法:

BiFunction<Doc, Throwable, Doc> customFailureMapper = { doc, e -> doc.copyWith( status: 'my-custom-fail-status' , error: e.message ) } new FeedHandler().handle(..., FeedHandler.DEFAULT_FILTER, FeedHandler.DEFAULT_SUCCESS_MAPPER, customFailureMapper )

如果您的語(yǔ)言支持,則可以通過(guò)分配默認(rèn)值來(lái)確保客戶端實(shí)際上不必提供每個(gè)參數(shù)。 我正在使用支持將默認(rèn)值分配給方法中的參數(shù)的Apache Groovy :

List<Doc> handle(List<Doc> changes, Function<Doc, Try<Resource>> creator, Predicate<Doc> filter = DEFAULT_FILTER, BiFunction<Doc, Resource, Doc> successMapper = DEFAULT_SUCCESS_MAPPER, BiFunction<Doc, Throwable, Doc> failureMapper = DEFAULT_FAILURE_MAPPER)

在我們將應(yīng)用另一個(gè)更改之前,請(qǐng)看一下代碼:

FeedHandler { class FeedHandler { private static final Predicate<Doc> DEFAULT_FILTER = { doc -> doc.type == 'important' } private static final BiFunction<Doc, Resource, Doc> DEFAULT_SUCCESS_MAPPER = { doc, resource -> doc.copyWith( status: 'processed' , apiId: resource.id ) } private static final BiFunction<Doc, Throwable, Doc> DEFAULT_FAILURE_MAPPER = { doc, e -> doc.copyWith( status: 'failed' , error: e.message ) } List<Doc> handle(List<Doc> changes, Function<Doc, Try<Resource>> creator, Predicate<Doc> filter = DEFAULT_FILTER, BiFunction<Doc, Resource, Doc> successMapper = DEFAULT_SUCCESS_MAPPER, BiFunction<Doc, Throwable, Doc> failureMapper = DEFAULT_FAILURE_MAPPER) { changes .findAll { filter } .collect { doc -> creator.apply(doc) .map { resource -> successMapper.apply(doc, resource) }.getOrElseGet { e -> failureMapper.apply(doc, e) } } } }

介紹兩者

您是否注意到以下部分?

.collect { doc -> creator.apply(doc) .map { resource -> successMapper.apply(doc, resource) }.getOrElseGet { e -> failureMapper.apply(doc, e) } }

請(qǐng)記住, creator的類(lèi)型是

Function<Doc, Try<Resource>>

表示它返回一個(gè)Try 。 我們?cè)诘?部分中介紹了Try ,它是從Scala等語(yǔ)言中借來(lái)的。

幸運(yùn)的是, collect { doc的“ doc”變量仍在傳遞給我們需要它的successMapper和failureMapper 范圍內(nèi) ,但是Try#map的方法簽名(接受一個(gè)Function )與我們的successMapper (即一個(gè)BiFunction 。 Try#getOrElseGet也是Try#getOrElseGet ,它也只需要一個(gè)Function

從Try Javadocs:

  • map(Function <?super T,?extended U>映射器)
  • getOrElseGet(Function <?super Throwable,?extended T> other)

簡(jiǎn)而言之,我們需要從

  • BiFunction <文檔,資源,文檔> successMapper
  • BiFunction <文檔,Throwable,文檔> failureMapper
  • 函數(shù)<資源,文檔> successMapper
  • 函數(shù)<Throwable,Doc> failureMapper
  • 同時(shí)仍然可以將原始文檔作為輸入 。

    讓我們介紹兩個(gè)簡(jiǎn)單的類(lèi)型,它們封裝了2個(gè)BiFunction的2個(gè)參數(shù):

    class CreationSuccess { Doc doc Resource resource } class CreationFailed { Doc doc Exception e }

    我們將論點(diǎn)從

  • BiFunction <文檔,資源,文檔> successMapper
  • BiFunction <文檔,Throwable,文檔> failureMapper
  • 改為功能

  • 函數(shù)<CreationSuccess,Doc> successMapper
  • 函數(shù)<CreationFailed,Doc> failureMapper
  • 現(xiàn)在, handle方法如下所示:

    List<Doc> handle(List<Doc> changes, Function<Doc, Try<Resource>> creator, Predicate<Doc> filter, Function<CreationSuccess, Doc> successMapper, Function<CreationFailed, Doc> failureMapper) { changes .findAll { filter } .collect { doc -> creator.apply(doc) .map(successMapper) .getOrElseGet(failureMapper) } }

    …… 但是還不行

    Try使map和getOrElseGet需要分別。 一個(gè)

    • 函數(shù)<資源,文檔> successMapper
    • 函數(shù)<Throwable,Doc> failureMapper

    這就是為什么我們需要將其更改為另一個(gè)著名的FP結(jié)構(gòu),稱為Either

    幸運(yùn)的是Vavr有要么太。 它的Javadoc說(shuō):

    任一代表兩種可能的值。

    通常使用這兩種類(lèi)型來(lái)區(qū)分正確的值(“正確”)或錯(cuò)誤的值。

    它變得非常抽象:

    一個(gè)Either可以是Either.Left或Either.Right。 如果給定的Either是Right并投影到Left,則Left操作對(duì)Right值沒(méi)有影響。 如果給定的Either是Left并投影到Right,則Right操作對(duì)Left值沒(méi)有影響。 如果將“左”投影到“左”或?qū)ⅰ坝摇蓖队暗健坝摇?#xff0c;則操作會(huì)生效。

    讓我解釋以上神秘的文檔。 如果我們更換

    Function<Doc, Try<Resource>> creator

    通過(guò)

    Function<Doc, Either<CreationFailed, CreationSuccess>> creator

    我們將CreationFailed分配給“ left”參數(shù),按照慣例通常會(huì)保留錯(cuò)誤(請(qǐng)參見(jiàn)Either上的Haskell文檔 ), CreationSuccess是“ right”(和“正確”)值。

    在運(yùn)行時(shí),該實(shí)現(xiàn)曾經(jīng)返回Try ,但是現(xiàn)在可以返回Either.Right ,如果成功,例如

    return Either.right( new CreationSuccess( doc: document, resource: [id: '7' ] ) )

    Either.Left ,但發(fā)生故障時(shí)除外- 兩者都包括原始文檔 。 是。

    因?yàn)楝F(xiàn)在類(lèi)型最終匹配,所以我們終于壓扁了

    .collect { doc -> creator.apply(doc) .map { resource -> successMapper.apply(doc, resource) }.getOrElseGet { e -> failureMapper.apply(doc, e) } }

    進(jìn)入

    .collect { doc -> creator.apply(doc) .map(successMapper) .getOrElseGet(failureMapper) }

    現(xiàn)在, handle方法如下所示:

    List<Doc> handle(List<Doc> changes, Function<Doc, Either<CreationFailed, CreationSuccess>> creator, Predicate<Doc> filter, Function<CreationSuccess, Doc> successMapper, Function<CreationFailed, Doc> failureMapper) { changes .findAll { filter } .collect { doc -> creator.apply(doc) .map(successMapper) .getOrElseGet(failureMapper) } }

    結(jié)論

    我可以說(shuō)我已經(jīng)實(shí)現(xiàn)了開(kāi)始時(shí)設(shè)定的大多數(shù)目標(biāo):

    • 是的,我設(shè)法避免了重新分配變量
    • 是的,我設(shè)法避免了可變數(shù)據(jù)結(jié)構(gòu)
    • 是的,我設(shè)法避免了狀態(tài) (至少在FeedHandler中)
    • 是的,我設(shè)法支持函數(shù) (使用某些Java內(nèi)置函數(shù)類(lèi)型和某些第三方庫(kù)Vavr)

    我們已經(jīng)將所有內(nèi)容移至函數(shù)簽名,以便FeedHandler的handle方法的調(diào)用者可以直接傳遞正確的實(shí)現(xiàn)。 如果您從頭到尾回顧原始版本,您會(huì)注意到在處理更改列表時(shí),我們?nèi)匀怀袚?dān)所有責(zé)任:

    • 通過(guò)某些條件過(guò)濾文檔列表
    • 為每個(gè)文檔創(chuàng)建資源
    • 成功創(chuàng)建資源后執(zhí)行一些操作
    • 無(wú)法創(chuàng)建資源時(shí)執(zhí)行其他操作

    然而,在第一部分中,這些責(zé)任是勢(shì)在必行寫(xiě)出來(lái),for語(yǔ)句聲明,都在一個(gè)大聚集在一起handle方法。 現(xiàn)在,最后,每個(gè)決定或動(dòng)作都由具有抽象名稱的函數(shù)表示,例如“過(guò)濾器”,“創(chuàng)建者”,“ successMapper”和“ failureMapper”。 實(shí)際上,它成為一個(gè)高階函數(shù),以多個(gè)函數(shù)之一作為參數(shù)。 提供所有參數(shù)的責(zé)任已經(jīng)轉(zhuǎn)移到了客戶的上層。 如果您查看GitHub項(xiàng)目,您會(huì)注意到,對(duì)于這些示例,我不得不不斷更新單元測(cè)試。

    有爭(zhēng)議的部分

    在實(shí)踐中,如果不需要,我可能不會(huì)編寫(xiě)我的(Java)業(yè)務(wù)代碼,例如FeedHandler類(lèi)在傳遞通用Java函數(shù)類(lèi)型(即Function , BiFunction , Predicate , Consumer , Supplier )方面的使用方式所有這些極端的靈活性。 所有這些都是以可讀性為代價(jià)的。 是的,Java是一種靜態(tài)類(lèi)型的語(yǔ)言,因此,使用泛型時(shí),必須在所有類(lèi)型參數(shù)中明確使用一種語(yǔ)言,從而導(dǎo)致以下功能的簽名困難:

    handle(List<Doc> changes, Function<Doc, Either<CreationFailed, CreationSuccess>> creator, Predicate<Doc> filter, Function<CreationSuccess, Doc> successMapper, Function<CreationFailed, Doc> failureMapper)

    在普通JavaScript中,您將沒(méi)有任何類(lèi)型,并且您必須閱讀文檔以了解每個(gè)參數(shù)的期望。

    handle = function (changes, creator, filter, successMapper, failureMapper)

    但是,這是一個(gè)折衷方案。 Groovy中,也是一個(gè)JVM語(yǔ)言, 可以讓我省略所有的例子類(lèi)型的信息在這個(gè)系列中,甚至允許我使用閉包(象Java lambda表達(dá)式)是在Groovy中的函數(shù)式編程范式的核心。

    更極端的做法是在類(lèi)級(jí)別指定所有類(lèi)型,以使客戶端具有最大的靈活性,以便為不同的FeedHandler實(shí)例指定不同的類(lèi)型。

    handle(List<T> changes, Function<T, Either<R, S>> creator, Predicate<T> filter, Function<S, T> successMapper, Function<R, T> failureMapper)

    什么時(shí)候合適?

    • 如果您完全控制代碼,則在特定上下文中使用它來(lái)解決特定問(wèn)題時(shí),這將過(guò)于抽象而無(wú)法產(chǎn)生任何收益。
    • 但是,如果我將一個(gè)庫(kù)或框架開(kāi)源(或者在一個(gè)組織內(nèi)向其他團(tuán)隊(duì)或部門(mén)使用),該庫(kù)或框架正在各種不同的用例中使用,那么我可能不會(huì)事先想到,為靈活性而設(shè)計(jì)可能值得。 讓呼叫者決定如何過(guò)濾以及成功或失敗的構(gòu)成是明智之舉。

    最終,上述內(nèi)容在API設(shè)計(jì) ,是和解耦方面都有所涉及,但是在典型的Enterprise Java Java項(xiàng)目中“使一切成為函數(shù)”可能需要與您和您的團(tuán)隊(duì)成員進(jìn)行一些討論。 多年來(lái),一些同事已經(jīng)習(xí)慣了一種更傳統(tǒng),更慣用的代碼編寫(xiě)方式。

    好的零件

    • 我絕對(duì)希望使用不可變的數(shù)據(jù)結(jié)構(gòu) (和“參照透明性”)來(lái)幫助推斷我的數(shù)據(jù)所處的狀態(tài)。想想Collections.unmodifiableCollection的集合。 在我的示例中,我將Groovy的@Immutable用于POJO,但在普通的Java庫(kù)(例如Immutables , AutoValue或Project Lombok)中也可以使用。
    • 最大的改進(jìn)實(shí)際上是導(dǎo)致了一種更具功能性的樣式:使代碼講故事 ,這主要是關(guān)于分離關(guān)注點(diǎn)并適當(dāng)?shù)孛挛铩?在任何編程風(fēng)格(即使是OO:D)中,這都是一個(gè)好習(xí)慣,但這確實(shí)消除了混亂,并允許引入(純)函數(shù)。
    • 在Java中,我們習(xí)慣于以特定方式進(jìn)行異常處理,以至于像我這樣的開(kāi)發(fā)人員很難提出其他解決方案。 諸如Haskell之類(lèi)的功能語(yǔ)言僅返回錯(cuò)誤代碼,因?yàn)椤?Niklaus Wirth認(rèn)為異常是GOTO的轉(zhuǎn)世,因此省略了它們” 。 在Java中,可以使用CompletableFuture或…
    • 通過(guò)引入第3方庫(kù)(例如Vavr)可在您自己的代碼庫(kù)中使用的特定類(lèi)型(例如Try和Either )可以極大地幫助您啟用以FP樣式編寫(xiě)的更多選項(xiàng) ! 我以流暢的方式編寫(xiě)“成功”或“失敗”路徑并具有很高的可讀性而感到非常著迷。

    Java不是F#的Scala或Haskell或Clojure,它最初遵循的是面向?qū)ο缶幊?#xff08;OOP)范例,就像C ++,C#,Ruby等一樣,但是在Java 8中引入了lambda表達(dá)式并結(jié)合了一些很棒的功能之后如今,開(kāi)放源代碼庫(kù)如今,開(kāi)發(fā)人員絕對(duì)可以選擇OOP和FP必須提供的最佳元素

    做系列的經(jīng)驗(yàn)教訓(xùn)

    我在很早以前就開(kāi)始了這個(gè)系列的討論 。 早在2017年,我發(fā)現(xiàn)自己在一段代碼上進(jìn)行了一些FP風(fēng)格的重構(gòu),這啟發(fā)了我去尋找一系列名為“ Functional Java by Example”的文章的示例 。 這成為我在每個(gè)批次中一直使用的FeedHandler代碼。

    那時(shí)我已經(jīng)對(duì)所有的代碼進(jìn)行了更改,但是當(dāng)我計(jì)劃編寫(xiě)實(shí)際的博客文章時(shí),我常常想到:“我只是不能展示重構(gòu),我必須進(jìn)行實(shí)際解釋!” 那就是我為自己設(shè)置陷阱的地方,因?yàn)樵谡麄€(gè)過(guò)程中,我坐下來(lái)寫(xiě)作的時(shí)間越來(lái)越少。 (任何寫(xiě)過(guò)博客的人都知道,簡(jiǎn)單地分享要點(diǎn)和撰寫(xiě)可理解的英語(yǔ)co的連貫段落在時(shí)間上的區(qū)別)

    下次當(dāng)我想到進(jìn)行一系列學(xué)習(xí)時(shí),我將向Google返回這些經(jīng)驗(yàn)教訓(xùn):

  • 如果您不準(zhǔn)備在發(fā)布新文章時(shí)每次準(zhǔn)備發(fā)布的每期文章都更新所有鏈接,則不要在每篇文章的頂部都包含目錄(TOC)。 如果將這些交叉發(fā)布到公司的公司博客中,則工作量是原來(lái)的2倍🙂
  • 隨著時(shí)間的流逝,您可能會(huì)得出自己寧愿偏離主要用例的結(jié)論,也就是剛開(kāi)始使用的Big Coding Example。 我寧愿展示更多的FP概念(例如, 使用FP技術(shù)時(shí)的咖喱,記憶,懶惰,以及不同的心態(tài)),但我不能很好地適應(yīng)以前完成的重構(gòu)和我在一開(kāi)始建立的TOC 。 如果您正在撰寫(xiě)有關(guān)特定概念的文章,通常會(huì)找到一個(gè)合適的示例來(lái)幫助說(shuō)明手頭的特定概念,并且仍然與讀者相關(guān)。 隨著時(shí)間的流逝,我將獲得更好的洞察力,從而可以確定接下來(lái)要寫(xiě)的更好的東西以及要使用的更合適的示例。 下次,我將不得不尋找一種方法來(lái)(更好地:允許)給自己一些創(chuàng)作上的自由😉
    • 《功能性思維:語(yǔ)法驚人的范式 》,尼爾·福特(Neil Ford)著,它展示了FP思維的新方法,并且也以不同的方式處理問(wèn)題。
    • 40分鐘內(nèi)的函數(shù)式編程 Russ Olsen的Youtube視頻解釋說(shuō):“這些數(shù)學(xué)家用379頁(yè)證明1 + 1 = 2。 讓我們看看我們可以從中吸取什么好主意。”
    • 為什么不對(duì)函數(shù)進(jìn)行規(guī)范編程? 理查德·費(fèi)爾德曼(Richard Feldman)的Youtube視頻,他解釋了為什么OOP變得非常流行,以及FP為何不是常態(tài)。 正如您所知,他是Elm核心團(tuán)隊(duì)的成員,與FP有一定的聯(lián)系。
    • (耦合)控制的倒置有關(guān)“托管功能”的深思熟慮的文章。 您想要抽象嗎?

    如果您有任何意見(jiàn)或建議,我很想聽(tīng)聽(tīng)他們的意見(jiàn)!

    編程愉快! 🙂


    翻譯自: https://www.javacodegeeks.com/2019/12/functional-java-by-example-part-8-more-pure-functions.html

    java 函數(shù)式編程 示例

    總結(jié)

    以上是生活随笔為你收集整理的java 函数式编程 示例_功能Java示例 第8部分–更多纯函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。