Java中使用Map and Fold进行功能性编程
免責(zé)聲明:我不是函數(shù)式編程的參考,本文不過是一個(gè)簡短的介紹; FP愛好者可能不太喜歡它。
您已經(jīng)熟悉了
想象一下不含增值稅的數(shù)量的List <Double>。 我們希望將此列表轉(zhuǎn)換為包含增值稅的另一個(gè)相應(yīng)列表。 首先,我們定義一種將增值稅加到一個(gè)單一金額中的方法:
public double addVAT(double amount, double rate) {return amount * (1 + rate);}現(xiàn)在讓我們將此方法應(yīng)用于列表中的每個(gè)金額:
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; }在這里,我們創(chuàng)建了另一個(gè)輸出列表,對(duì)于輸入列表的每個(gè)元素,我們對(duì)其應(yīng)用了addVAT()方法,然后將結(jié)果存儲(chǔ)到大小完全相同的輸出列表中。 恭喜,正如我們剛剛手工完成的,在方法addVAT()的輸入列表上有一個(gè)Map。 讓我們再做一次。
現(xiàn)在我們要使用貨幣匯率將每個(gè)金額轉(zhuǎn)換為另一種貨幣,因此我們需要一種新的方法:
公共double convertCurrency(double
public double convertCurrency(double amount, double currencyRate){return amount / currencyRate;}現(xiàn)在,我們可以將此方法應(yīng)用于列表中的每個(gè)元素:
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處調(diào)用的方法外,兩種接受列表的方法是如何相似的:
您經(jīng)常在Java中執(zhí)行此操作,而這恰恰是Map運(yùn)算符:將給定方法someMethod (T):T應(yīng)用于list <T>的每個(gè)元素,這將為您提供另一個(gè)相同大小的list <T>。
功能語言認(rèn)識(shí)到這種特殊需求(在集合的每個(gè)元素上應(yīng)用一種方法)非常普遍,因此他們將其直接封裝到內(nèi)置的Map運(yùn)算符中。 這樣,給定addVAT(double,double)方法,我們可以使用Map運(yùn)算符直接編寫如下代碼:
List amountsWithVAT = map (addVAT, amounts, rate)是的,第一個(gè)參數(shù)是一個(gè)函數(shù),因?yàn)楹瘮?shù)是函數(shù)語言中的一等公民,因此可以將它們作為參數(shù)傳遞。 與for循環(huán)相比,使用Map運(yùn)算符更簡潔,更不易出錯(cuò),其意圖也更加明確,但是我們在Java中沒有它……
因此,這些示例的要點(diǎn)是,您已經(jīng)不熟悉甚至不知道函數(shù)式編程的關(guān)鍵概念:Map運(yùn)算符。
現(xiàn)在是Fold運(yùn)算符
回到金額列表,現(xiàn)在我們需要將總金額計(jì)算為每個(gè)金額的總和。 超級(jí)容易,讓我們循環(huán)執(zhí)行一下:
public double totalAmount(List<Double> amounts){double sum = 0;for(double amount : amounts){sum += amount;}return sum; }基本上,我們只是在列表上進(jìn)行了折疊,使用函數(shù)“ + =”將每個(gè)元素折疊成一個(gè)元素,這里是一個(gè)遞增的數(shù)字,一次折疊一個(gè)。 這類似于Map運(yùn)算符,除了結(jié)果不是列表而是單個(gè)元素(標(biāo)量)。
這又是您通常用Java編寫的那種代碼,現(xiàn)在您已經(jīng)用功能語言為其命名:“ Fold ”或“ Reduce”。 Fold運(yùn)算符通常在函數(shù)式語言中是遞歸的,因此我們在此不再對(duì)其進(jìn)行描述。 但是,我們可以使用某種可變狀態(tài)在迭代之間累加結(jié)果,從而以迭代形式實(shí)現(xiàn)相同的目的。 在這種方法中,Fold采用一種內(nèi)部可變狀態(tài)的方法,該方法期望一個(gè)元素,例如someMethod(T),并將其反復(fù)應(yīng)用于輸入列表<T>中的每個(gè)元素,直到我們得到一個(gè)單個(gè)元素T,即折疊操作的結(jié)果。
與Fold一起使用的典型函數(shù)是求和,邏輯與和或,List.add()或List.addAll(),StringBuilder.append(),max或min等。Fold的思維方式類似于SQL中的聚合函數(shù) 。
形狀思考
直觀地思考(帶有草率的圖片),Map接收一個(gè)大小為n的列表,并返回另一個(gè)大小相同的列表:
另一方面,Fold獲取大小為n的列表,并返回單個(gè)元素(標(biāo)量):
您可能還記得我以前關(guān)于謂詞的文章 ,這些文章通常用于將集合過濾為元素較少的集合。 實(shí)際上,此過濾器運(yùn)算符是在大多數(shù)功能語言中補(bǔ)充Map和Fold的第三種標(biāo)準(zhǔn)運(yùn)算符。
Eclipse模板
由于Map和Fold很常見,因此有必要為它們創(chuàng)建Eclipse模板,例如Map:
在Java中更接近地圖和折疊
Map和Fold是期望函數(shù)作為參數(shù)的構(gòu)造,而在Java中,傳遞方法的唯一方法是將其包裝到接口中。
在Apache Commons Collections中,有兩個(gè)接口對(duì)于我們的需求特別有趣: Transformer (具有一個(gè)方法transform(T):T )和Closure (具有一個(gè)方法execute(T):void) 。 類CollectionUtils提供了collect(Iterator,Transformer)方法(它基本上是Java集合的窮人Map運(yùn)算符)以及forAllDo()方法,該方法可以使用閉包來模擬Fold運(yùn)算符。
使用Google Guava, Iterables類提供了靜態(tài)方法transform(Iterable,Function) ,該方法基本上是Map運(yùn)算符。
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運(yùn)算符,可以使用Closure接口,例如Apache Commons Collection中的Closure接口,僅使用一個(gè)帶有一個(gè)參數(shù)的單一方法,因此您必須在內(nèi)部保留當(dāng)前的-mutable-狀態(tài),就像'+ =確實(shí)。
不幸的是,盡管Guava中沒有Fold,盡管它經(jīng)常被要求提供 ,甚至沒有類似閉包的函數(shù),但是創(chuàng)建自己的函數(shù)并不難,例如,您可以使用以下方法實(shí)現(xiàn)上述總計(jì):
// 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的功能不僅限于簡單的集合,還可以擴(kuò)展到任何可導(dǎo)航的結(jié)構(gòu),尤其是樹和圖。
想象一棵使用節(jié)點(diǎn)類及其子節(jié)點(diǎn)的樹。 將深度優(yōu)先搜索和廣度優(yōu)先搜索(DFS和BFS)編碼為兩個(gè)接受Closure作為單個(gè)參數(shù)的通用方法,可能是一個(gè)好主意:
public class Node ...{...public void dfs(Closure closure){...}public void bfs(Closure closure){...} }我過去經(jīng)常使用這種技術(shù),我可以說它可以大大減少類的大小,僅使用一種通用方法,而不是許多看起來相似的方法(每個(gè)方法都會(huì)重做自己的樹遍歷)。 更重要的是,可以使用模擬閉包對(duì)遍歷本身進(jìn)行單元測試。 每個(gè)封蓋也可以獨(dú)立進(jìn)行單元測試,所有這些都使您的生活變得更加簡單。
訪客模式可以實(shí)現(xiàn)非常相似的想法,您可能已經(jīng)很熟悉。 我在我的代碼和其他幾個(gè)團(tuán)隊(duì)的代碼中多次看到,Visitor非常適合在遍歷數(shù)據(jù)結(jié)構(gòu)期間累積狀態(tài)。 在這種情況下,Visitor只是閉合中傳遞給折疊的一種特殊情況。
Map-Reduce上的一個(gè)字
您可能聽說過Map-Reduce模式,是的,其中的“ Map”和“ Reduce”一詞指的是我們剛剛看到的相同的函數(shù)運(yùn)算符Map和Fold(也稱為Reduce)。 即使實(shí)際應(yīng)用更加復(fù)雜,也很容易注意到Map 令人尷尬地是并行的,這對(duì)并行計(jì)算有很大幫助。
參考: 與我們的JCG合作伙伴一起 在您的日常Java中使用Map和Fold進(jìn)行函數(shù)式編程的思考 ? Cyrille Martraire 博客上的Cyrille Martraire 。
翻譯自: https://www.javacodegeeks.com/2012/03/functional-programming-with-map-and.html
總結(jié)
以上是生活随笔為你收集整理的Java中使用Map and Fold进行功能性编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 司马懿后代现在还有吗(司马懿还有后人在世
- 下一篇: Java:伪造工厂的闭包以创建域对象