fold函数_Java中使用Map and Fold进行函数式编程
fold函數
在函數式編程中,Map和Fold是兩個非常有用的運算符,它們屬于每種函數式語言。 如果Map和Fold運算符是如此強大且必不可少,那么您如何解釋說即使Java編程語言缺少這兩個運算符,我們也可以使用Java來完成工作? 事實是,使用Java進行編碼時,您已經進行了Map和Fold,只是每次都使用循環手動進行操作。免責聲明:我不是函數式編程的參考,本文不過是一個簡短的介紹。 FP愛好者可能不太喜歡它。
您已經熟悉了
想象一下不含增值稅的數量的List <Double>。 我們希望將此列表轉換為包含增值稅的另一個相應列表。 首先,我們定義一種將增值稅加到一個單一金額中的方法:
public double addVAT(double amount, double rate) {return amount * (1 + rate);}現在讓我們將此方法應用于列表中的每個金額:
public List<Double> addVAT(List<Double> amounts, double rate){final List<Double> amountsWithVAT = new ArrayList<Double>();for(double amount : amounts){amountsWithVAT.add(addVAT(amount, rate));}return amountsWithVAT; }在這里,我們創建了另一個輸出列表,對于輸入列表的每個元素,我們對其應用addVAT()方法,然后將結果存儲到大小完全相同的輸出列表中。 恭喜,正如我們剛剛手工完成的,在方法addVAT()的輸入列表上有一個Map。 讓我們再做一次。
現在我們要使用貨幣匯率將每個金額轉換為另一種貨幣,因此我們需要一種新的方法:
public double convertCurrency(double
public double convertCurrency(double amount, double currencyRate){return amount / currencyRate;}現在,我們可以將此方法應用于列表中的每個元素:
public List<Double> convertCurrency(List<Double> amounts, double currencyRate){final List<Double> amountsInCurrency = new ArrayList<Double>();for(double amount : amounts){amountsInCurrency.add(convertCurrency(amount, currencyRate));}return amountsInCurrency; }注意,除了在步驟2處調用的方法外,兩種接受列表的方法是如何相似的:
您經常在Java中執行此操作,而這恰恰是Map運算符:將給定方法someMethod (T):T應用于list <T>的每個元素,這將為您提供另一個相同大小的list <T>。
功能語言認識到這種特殊需求(在集合的每個元素上應用一種方法)非常普遍,因此他們將其直接封裝到內置的Map運算符中。 這樣,給定addVAT(double,double)方法,我們可以使用Map運算符直接編寫如下代碼:
List amountsWithVAT = map (addVAT, amounts, rate)是的,第一個參數是函數,因為函數是函數語言中的一等公民,因此可以將它們作為參數傳遞。 與for循環相比,使用Map運算符更簡潔,更不易出錯,其意圖也更加明確,但是我們在Java中沒有它……
因此,這些示例的要點在于,您已經不熟悉甚至不知道函數式編程的關鍵概念:Map運算符。
現在是Fold運算符
回到金額列表,現在我們需要將總金額計算為每個金額的總和。 超級容易,讓我們循環執行一下:
public double totalAmount(List<Double> amounts){double sum = 0;for(double amount : amounts){sum += amount;}return sum; }基本上,我們只是在列表上進行了折疊,使用函數“ + =”將每個元素折疊為一個元素,這里是一個數字,一次遞增地折疊。 這類似于Map運算符,除了結果不是列表而是單個元素(標量)。
這又是您通常用Java編寫的那種代碼,現在您已經用功能語言為它命名:“ Fold ”或“ Reduce”。 Fold運算符通常在函數式語言中是遞歸的,因此在此不再對其進行描述。 但是,我們可以使用某種可變狀態在迭代之間累加結果,從而以迭代形式實現相同的目的。 在這種方法中,Fold采用一種內部可變狀態的方法,該方法期望一個元素,例如someMethod(T),并將其重復應用于輸入列表<T>中的每個元素,直到我們得到一個單個元素T,即折疊操作的結果。
與Fold一起使用的典型函數是求和,邏輯與與或,List.add()或List.addAll(),StringBuilder.append(),max或min等。Fold的思維方式類似于SQL中的聚合函數 。
形狀思考
直觀地思考(帶有草率的圖片),Map接收一個大小為n的列表,并返回另一個大小相同的列表:
另一方面,Fold獲取大小為n的列表,并返回單個元素(標量):
您可能還記得我之前關于謂詞的文章 ,這些文章通常用于將集合過濾為元素較少的集合。 實際上,此過濾器運算符是在大多數功能語言中補充Map和Fold的第三種標準運算符。
Eclipse模板
由于Map和Fold很常見,因此有必要為它們創建Eclipse模板,例如Map:
在Java中更接近地圖和折疊
Map和Fold是期望函數作為參數的構造,而在Java中,傳遞方法的唯一方法是將其包裝到接口中。
在Apache Commons Collections中,有兩個接口對于我們的需求特別有趣: Transformer (具有一個方法transform(T):T )和Closure (具有一個方法execute(T):void) 。 類CollectionUtils提供了方法collect(Iterator,Transformer) ,它基本上是Java集合的窮人Map運算符,以及提供了可以使用閉包來模擬Fold運算符的forAllDo()方法。
使用Google Guava, Iterables類提供了靜態方法transform(Iterable,Function) ,該方法基本上是Map運算符。
List<Double> exVat = Arrays.asList(new Double[] { 99., 127., 35. });Iterable<Double> incVat = Iterables.transform(exVat, new Function<Double, Double>() {public Double apply(Double exVat) {return exVat * (1.196);}});System.out.println(incVat); //print [118.404, 151.892, 41.86]類似的變換()方法,也可在類解釋為解釋和地圖為地圖。
要在Java中模擬Fold運算符,可以使用Closure接口,例如Apache Commons Collection中的Closure接口,僅使用一個帶有一個參數的單一方法,因此必須在內部保留當前的-mutable-狀態,就像'+ =確實。
不幸的是,Guava中沒有Fold,盡管它經常被要求提供 ,甚至沒有類似閉包的函數,但是創建自己的函數并不難,例如,您可以使用以下方式實現上述總計:
// the closure interface with same input/output type public interface Closure<T> {T execute(T value); }// an example of a concrete closure public class SummingClosure implements Closure<Double> {private double sum = 0;public Double execute(Double amount) {sum += amount; // apply '+=' operatorreturn sum; // return current accumulated value} }// the poor man Fold operator public final static <T> T foreach(Iterable<T> list, Closure<T> closure) {T result = null;for (T t : list) {result = closure.execute(t);}return result; }@Test // example of use public void testFold() throws Exception {SummingClosure closure = new SummingClosure();List<Double> exVat = Arrays.asList(new Double[] { 99., 127., 35. });Double result = foreach(exVat, closure);System.out.println(result); // print 261.0 }不僅用于收藏:可折疊在樹木和其他建筑物上
Map and Fold的功能不僅限于簡單的集合,還可以擴展到任何可導航的結構,尤其是樹和圖。
想象一棵使用帶有其子節點的類Node的樹。 最好將深度優先搜索和廣度優先搜索(DFS和BFS)編碼成兩個接受Closure作為單個參數的通用方法:
public class Node ...{...public void dfs(Closure closure){...}public void bfs(Closure closure){...} }過去,我經常使用這種技術,我可以說它可以大大減少類的大小,僅使用一種通用方法,而不是許多看起來相似的方法(每個方法都會重做自己的樹遍歷)。 更重要的是,可以使用模擬閉包對遍歷本身進行單元測試。 每個封蓋也可以獨立進行單元測試,所有這些都使您的生活變得更加簡單。
訪客模式可以實現非常相似的想法,您可能已經很熟悉。 我在代碼和其他幾個團隊的代碼中多次看到,Visitor非常適合在遍歷數據結構期間累積狀態。 在這種情況下,Visitor只是閉合中傳遞給折疊的一種特殊情況。
Map-Reduce上的一個字
您可能聽說過Map-Reduce模式,是的,其中的“ Map”和“ Reduce”一詞指的是我們剛剛看到的相同的函數運算符Map和Fold(也稱為Reduce)。 盡管實際應用更加復雜,但很容易注意到Map 令人尷尬地是并行的,這對并行計算有很大幫助。
參考:來自我們的JCG合作伙伴的 日常Java中的Map和Fold功能編程思考 ? Cyrille Martraire 博客上的Cyrille Martraire 。
翻譯自: https://www.javacodegeeks.com/2012/03/functional-programming-with-map-and.html
fold函數
總結
以上是生活随笔為你收集整理的fold函数_Java中使用Map and Fold进行函数式编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javaone_虚拟化Java应用程序:
- 下一篇: 康熙九子夺嫡哪些阿哥没有参与(没参与阿哥