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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

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

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

java 函數式編程 示例

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

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

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

如果您是第一次來,最好是從頭開始閱讀。 它有助于了解我們從何處開始以及如何在整個系列中繼續前進。

這些都是這些部分:

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

我將在每篇文章發表時更新鏈接。 如果您通過內容聯合組織來閱讀本文,請查看我博客上的原始文章。

每次代碼也被推送到這個GitHub項目 。

最大化運動部件

您可能已經聽過Micheal Feathers的以下短語:

OO通過封裝運動部件使代碼易于理解。 FP通過最大程度地減少運動部件來使代碼易于理解。

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

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 ) } }

替換為功能類型

我們可以使用對函數接口類型的變量(例如Predicate或BiFunction的引用來替換每種方法。

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

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

謂詞

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

B),我們可以替換一個接受2個參數并返回結果的方法

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 -> ... }

為了實際調用封裝在(Bi)Function中的邏輯,我們必須對其調用apply 。 結果如下:

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 ) } }

將所有輸入移至功能本身

我們將所有內容移至方法簽名,以便FeedHandler的handle方法的調用者可以提供自己的那些功能的實現。

方法簽名將更改為:

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)

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

  • isImportant
  • setToProcessed
  • setToFailed

轉換為類頂部的新常量 ,反映它們的新作用。

  • DEFAULT_FILTER
  • DEFAULT_SUCCESS_MAPPER
  • DEFAULT_FAILURE_MAPPER

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

例如,當僅需要定制故障處理時,可以這樣調用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 )

如果您的語言支持,則可以通過分配默認值來確??蛻舳藢嶋H上不必提供每個參數。 我正在使用支持將默認值分配給方法中的參數的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)

在我們將應用另一個更改之前,請看一下代碼:

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) } }

請記住, creator的類型是

Function<Doc, Try<Resource>>

表示它返回一個Try 。 我們在第7部分中介紹了Try ,它是從Scala等語言中借來的。

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

從Try Javadocs:

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

簡而言之,我們需要從

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

    讓我們介紹兩個簡單的類型,它們封裝了2個BiFunction的2個參數:

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

    我們將論點從

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

  • 函數<CreationSuccess,Doc> successMapper
  • 函數<CreationFailed,Doc> failureMapper
  • 現在, 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需要分別。 一個

    • 函數<資源,文檔> successMapper
    • 函數<Throwable,Doc> failureMapper

    這就是為什么我們需要將其更改為另一個著名的FP結構,稱為Either 。

    幸運的是Vavr有要么太。 它的Javadoc說:

    任一代表兩種可能的值。

    通常使用這兩種類型來區分正確的值(“正確”)或錯誤的值。

    它變得非常抽象:

    一個Either可以是Either.Left或Either.Right。 如果給定的Either是Right并投影到Left,則Left操作對Right值沒有影響。 如果給定的Either是Left并投影到Right,則Right操作對Left值沒有影響。 如果將“左”投影到“左”或將“右”投影到“右”,則操作會生效。

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

    Function<Doc, Try<Resource>> creator

    通過

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

    我們將CreationFailed分配給“ left”參數,按照慣例通常會保留錯誤(請參見Either上的Haskell文檔 ), CreationSuccess是“ right”(和“正確”)值。

    在運行時,該實現曾經返回Try ,但是現在可以返回Either.Right ,如果成功,例如

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

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

    因為現在類型最終匹配,所以我們終于壓扁了

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

    進入

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

    現在, 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) } }

    結論

    我可以說我已經實現了開始時設定的大多數目標:

    • 是的,我設法避免了重新分配變量
    • 是的,我設法避免了可變數據結構
    • 是的,我設法避免了狀態 (至少在FeedHandler中)
    • 是的,我設法支持函數 (使用某些Java內置函數類型和某些第三方庫Vavr)

    我們已經將所有內容移至函數簽名,以便FeedHandler的handle方法的調用者可以直接傳遞正確的實現。 如果您從頭到尾回顧原始版本,您會注意到在處理更改列表時,我們仍然承擔所有責任:

    • 通過某些條件過濾文檔列表
    • 為每個文檔創建資源
    • 成功創建資源后執行一些操作
    • 無法創建資源時執行其他操作

    然而,在第一部分中,這些責任是勢在必行寫出來,for語句聲明,都在一個大聚集在一起handle方法。 現在,最后,每個決定或動作都由具有抽象名稱的函數表示,例如“過濾器”,“創建者”,“ successMapper”和“ failureMapper”。 實際上,它成為一個高階函數,以多個函數之一作為參數。 提供所有參數的責任已經轉移到了客戶的上層。 如果您查看GitHub項目,您會注意到,對于這些示例,我不得不不斷更新單元測試。

    有爭議的部分

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

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

    在普通JavaScript中,您將沒有任何類型,并且您必須閱讀文檔以了解每個參數的期望。

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

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

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

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

    什么時候合適?

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

    最終,上述內容在API設計 ,是和解耦方面都有所涉及,但是在典型的Enterprise Java Java項目中“使一切成為函數”可能需要與您和您的團隊成員進行一些討論。 多年來,一些同事已經習慣了一種更傳統,更慣用的代碼編寫方式。

    好的零件

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

    Java不是F#的Scala或Haskell或Clojure,它最初遵循的是面向對象編程(OOP)范例,就像C ++,C#,Ruby等一樣,但是在Java 8中引入了lambda表達式并結合了一些很棒的功能之后如今,開放源代碼庫如今,開發人員絕對可以選擇OOP和FP必須提供的最佳元素 。

    做系列的經驗教訓

    我在很早以前就開始了這個系列的討論 。 早在2017年,我發現自己在一段代碼上進行了一些FP風格的重構,這啟發了我去尋找一系列名為“ Functional Java by Example”的文章的示例 。 這成為我在每個批次中一直使用的FeedHandler代碼。

    那時我已經對所有的代碼進行了更改,但是當我計劃編寫實際的博客文章時,我常常想到:“我只是不能展示重構,我必須進行實際解釋!” 那就是我為自己設置陷阱的地方,因為在整個過程中,我坐下來寫作的時間越來越少。 (任何寫過博客的人都知道,簡單地分享要點和撰寫可理解的英語co的連貫段落在時間上的區別)

    下次當我想到進行一系列學習時,我將向Google返回這些經驗教訓:

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

    如果您有任何意見或建議,我很想聽聽他們的意見!

    編程愉快! 🙂


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

    java 函數式編程 示例

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。