深入理解函數語言程式設計

程式設計一生發表於2021-11-08

函數語言程式設計是對行為進行抽象。

程式設計一生,公眾號:程式設計一生架構之思-分析那些深入骨髓的設計原則

這句話比較難理解,換句話來說:函數語言程式設計是給自己的物件整容,有可能整的和原來差不多,也有可能整的看起來判若兩人,但是隻能處理這個物件,不會對函式外的其他資料產生影響。

函數語言程式設計又結合了lambda表示式和stream API。有些朋友反饋說:函數語言程式設計可讀性不好;還有些朋友反饋說:函數語言程式設計比較難debug。你們說的都對,但是有解決的辦法,看完這篇文章就明白了。

文章整體大綱如下:

lambda表示式

lambda表示式的本質是匿名內部類

先來看一個例子:杜甫的《登高》寫的好,被稱為千古律詩之首。

我從十幾歲的時候開始就一直在想這首詩怎麼不押韻:渚清沙白鳥飛回,高中老師講過古語裡“回”念huai,這就壓上韻了。但是潦倒新停濁酒杯的杯在古語或者古語方言裡念bai嗎?直到如今我還是沒有考證到是否是這樣。我就當它是念bai吧。

這首詩我最喜歡的四句,渲染磅礴的氣勢都含了數字:萬里、百年、無邊、不盡。我突發奇想:讓電腦來給這四句排排序吧。於是我寫了下面的程式:

public void sortDengGao() {
List<String> list = Lists.newArrayList("無邊落木蕭蕭下","不盡長江滾滾來","萬里悲秋常作客","百年多病獨登臺");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.substring(0, 2).compareTo(o2.substring(0, 2));
}
});
System.out.println(list);
}

我滑鼠點在new Comparator上,提示我匿名內部類可以用lambda代替:

使用Alt+Enter快捷鍵,我回車一下,結果變成了這樣:

public void sortDengGao() {
List<String> list = Lists.newArrayList("無邊落木蕭蕭下","不盡長江滾滾來","萬里悲秋常作客","百年多病獨登臺");
list.sort((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2)));
System.out.println(list);
}

當然還可以再執行一次Alt+Enter還原回來。

匿名內部類和lambda表示式既然可以使用快捷鍵相互轉換,那就說明他們本質上是一個東西。這樣就好理解了:在jdk1.8之前,通過匿名內部類訪問區域性變數必須要加final關鍵字,jdk1.8之後不需要顯示的加final關鍵字但實際上還是需要被訪問的變數不可變。這就對應了函數語言程式設計不會對函式外的其他資料產生影響。

lambda表示式的省略規則

lambda表示式核心是(對於匿名內部類)採用可推導可省略的原則。所以有些朋友反饋說:函數語言程式設計可讀性不好。因為它做了省略,如果對原本被省略的匿名內部類不熟悉,閱讀就會麻煩些。還有一點哈,編寫程式碼注意換行哈:

list.stream().sorted((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2))).close();

這段程式碼一個方法用一行是不是好看一些:

list.stream()
.sorted((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2)))
.close();

lambda表示式是對匿名內部類的下面三點做了省略:

  • 引數型別可省略

  • 方法只有一句程式碼時:大括號、return和分號可省略

  • 方法只有一個引數時小括號可以省略

 

stream API

stream API就是運用fluent風格的一個特例

對fluent風格不熟悉的強烈建議看看我之前的這篇《程式碼榮辱觀-以運用風格為榮,以隨意編碼為恥》。這篇文章邏輯清晰,語言詼諧,比喻恰當,專治不明白。

這裡只舉個簡單的例子:StringBuilder一般是這樣使用的:

new StringBuilder().append(1).append(2).toString();

這是典型的fluent風格。我們們來看它包含幾部分:

第一部分:new StringBuffer()構造一個特定物件

第二部分:append()對這個物件本身做處理,可以多次呼叫,每次都返回它本身

第三部分:toString()結束處理

再來看這個stream API的例子:

list.stream()
.sorted((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2)))
.close();

第一部分:.stream()構造一個特定物件

第二部分:sorted()對這個物件本身做處理,可以多次呼叫,每次都返回它本身

第三部分:close()結束處理

是不是一毛一樣!stream API就是運用fluent風格的一個特例,如此而已。所以我們要關注的點只是.stream()構造的特定物件Stream給我提供了怎麼的功能,達到了號稱比sql還簡單、還強大的功能。

一分鐘理解MapReduce

MapReduce現在流行於大資料中的概念,本質上是為了解決對於資料的平行計算方法明明本質上都是採用分治法,但是缺少高層並行程式設計模型,程式設計師需要自行指定儲存、計算、分發等任務的問題。MapReduce借鑑了Lisp函式式語言中的思想,用map和reduce兩個函式提供了高層的併發程式設計模型的抽象。

直白點說就是提供了一個計算手腳架,照著這個架子做開發就可以了。

上面圖中可以看到map的主要功能是把資料分成小塊進行計算,reduce是將小塊計算結果進行合併。在stream API中map和reduce功能也是一樣的。舉個例子:

public void mapReduceDengGao() {
List<String> list = Lists.newArrayList("無邊落木蕭蕭下","不盡長江滾滾來","萬里悲秋常作客","百年多病獨登臺");
String result = list.stream()
.map(word->word+"\n")
.reduce((a,b)->a+""+b)
.get();
System.out.println(result);
}

上面函式先用map方法把list每個元素都進行了處理:後面加換行符。然後用reduce方法對資料合併計算:合併為一個字串。大資料的MapReduce也就是幹了這!

用Intellij對stream API做debug

有些朋友反饋說:函數語言程式設計比較難debug。stream trace瞭解一下。

首先在steam API的地方打上斷點。當執行到斷點處,點選上面紅色框框裡那個圖示,之後會彈出一個框,但是可能一開始沒有資料,提示正在計算,稍等一會之後stream的每一步呼叫結果都可以看到啦:

 

總結

函數語言程式設計的優勢:

  • 程式碼可讀性高

  • 大資料量下處理集合效率高

  • 消滅巢狀地獄

程式碼可讀性高,上面已經講過了,因為簡潔明瞭。

大資料量下處理集合效率高,這個主要是指因為函數語言程式設計功能內聚,JVM優化時去掉了多餘的鎖。像上面MapReduce那段講的,採用分治法,使用並行流的話內部做了很好的多執行緒處理。

消滅巢狀地獄嘛,看看下面箭頭形程式碼:

用函數語言程式設計效果是這樣的,好看不好看不好說,起碼sonar靜態檢查能過:

 

推薦閱讀

年紀大了,是否該往管理方向轉型?

演講稿:新人培養之道

最近做code review的5點經驗分享

《躍遷-成為高手的技術》感悟

相關文章