Java 8 函數(shù)式編程
本次主題主要介紹什么是函數(shù)式編程,其主要特點, 以及它在 Java8 中是怎么體現(xiàn)的.
函數(shù)式編程這個思維早在1958年就被提出來的了,在60年后的今天重新被提出來,且越來越流行,其主要原因是 CPU 的時鐘頻率發(fā)展停滯不前,目前徘徊在4GHZ左右糯笙。
CPU 的時鐘頻率越快,則運行速度越快,但目前的工藝技術(shù)限制了 CPU 頻率的進一步提升,主流的芯片廠商目前都轉(zhuǎn)向了多核處理器這個架構(gòu)方向,所以我們編程不能再依賴時鐘頻率提高現(xiàn)有代碼的計算能力,需要充分利用現(xiàn)在 CPU 架構(gòu),而這唯一的辦法就是編寫并行化的代碼.
“學(xué)習(xí)一種全新的編程范式仓洼,困難并不在于掌握新的語言生年。
畢竟能拿起這本書的讀者邓夕,學(xué)過的編程語言少說也有一籮筐——語法不過是些小細節(jié)罷了。
真正考驗人的,是怎么學(xué)會用另一種方式去思考 。”
摘錄來自: [美] 福特(Neal Ford). “函數(shù)式編程思維 (圖靈程序設(shè)計叢書)”苇羡。
函數(shù)式編程思想,可以編寫出簡潔,高效的并行代碼,這也就是為什么現(xiàn)在又將函數(shù)式編程思維提出來,且越來越成為一種趨勢的原因.
什么是函數(shù)式編程?
計算機編程語言的分類:
機器語言
匯編語言
高級語言
轉(zhuǎn)換方式 | 客觀描述 | 編程范型 |
---|---|---|
編譯型 | 面向過程 | 命令式 |
解釋型 | 面向?qū)ο?/td> | 函數(shù)式 |
轉(zhuǎn)換方式
編譯型,即事先將程序翻譯成了 CPU 指令,只能運行在特地的環(huán)境里(CPU, 架構(gòu)等,例如 C)
解釋性,即邊解析程序邊翻譯成 CPU 指令,一般和平臺無關(guān)(例如 Java).
說通俗點, 編譯型語言即把東西煮熟了,可以直接開吃,而解釋型語言是火鍋,要邊煮邊吃.
所以編譯型語言吃的快,解釋性語言吃的慢.
Java 雖然會將代碼編程成 class 文件,但 class 文件本質(zhì)上是字節(jié)碼,并不是 CPU 指令,所以不是編譯型語言.
客觀描述
說面向過程和面向?qū)ο?我們先來說說寫史書的兩種方式:編年體和紀傳體.
編年體是按照時間來寫,比如說某一年發(fā)生了什么大事鼻弧、誰誰干了什么设江,第二年又有什么大事、誰誰干了什么攘轩,這么寫叉存。
紀傳體就是為某人立傳的形式來寫。以一個人為主線度帮。比如說歼捏,某某,生于多少年笨篷,哪一年干了什么瞳秽,哪一年又干了些什么,一直寫下去率翅。而且并不一定嚴格按照時間為順序,寫書的人可能會采用倒敘等方式敘述.
而編年體和紀傳體就是對應(yīng)著面向過程和面向?qū)ο?面向過程注重的是步驟,而面向?qū)ο髣t以對象為主,著重于解決問題的思路
編程范式
命令式編程是面向計算機硬件的抽象练俐,有變量(對應(yīng)著存儲單元),賦值語句(獲取安聘,存儲指令)痰洒,表達式(內(nèi)存引用和算術(shù)運算)和控制語句(跳轉(zhuǎn)指令)瓢棒,一句話浴韭,命令式程序就是一個馮諾依曼機的指令序列。
而函數(shù)式編程是面向數(shù)學(xué)的抽象脯宿,將計算描述為一種表達式求值念颈,一句話,函數(shù)式程序就是一個表達式连霉。
這里只要的區(qū)別是在寫代碼的過程的思維,而不是編譯后的結(jié)果.
可能一個函數(shù)式語言,經(jīng)過編譯后最后成了一條條 CPU 指令,但是并不能說明這個語言不是函數(shù)式語言.
函數(shù)式編程
以 Java 為例:
String getName(){
return name;
}
上述代碼, getName 我們通常稱之為 "方法", 也可以稱之為 "函數(shù)".
方法與函數(shù)到底有什么區(qū)別呢?
兩個稱呼不同其實編程范式思想.
方法,即注重解決問題的步驟,過程. 函數(shù),則注重類型之間的代數(shù)關(guān)系
這里所說的函數(shù),指的是數(shù)學(xué)上的函數(shù).
讓我們先回顧一下中學(xué)數(shù)學(xué),函數(shù)是怎么定義的:
函數(shù)的定義:給定一個數(shù)集A榴芳,假設(shè)其中的元素為x∥嗣遥現(xiàn)對A中的元素x施加對應(yīng)法則f,記作f(x)窟感,得到另一數(shù)集B讨彼。假設(shè)B中的元素為y。則y與x之間的等量關(guān)系可以用y=f(x)表示柿祈。我們把這個關(guān)系式就叫函數(shù)關(guān)系式哈误,簡稱函數(shù)。函數(shù)概念含有三個要素:定義域A躏嚎、值域C和對應(yīng)法則f蜜自。其中核心是對應(yīng)法則f,它是函數(shù)關(guān)系的本質(zhì)特征卢佣。
其中這個法則 f, 在函數(shù)式編程中就稱之為 <b>函數(shù)</b>,所謂的函數(shù)式編程,即編寫代碼的時候,注重的是類型之間的代數(shù)關(guān)系 fun.
舉個例子:
在學(xué)習(xí)編程的時候,有一個流傳了很廣的??→ 交換 A,B 兩數(shù)
//方法思想
void change(int a,int b){
int c = a;
a = b;
b = c;
System.out.println("a: " + a + " b:" + b);
}
//函數(shù)思想
void change(int a,int b){
System.out.println("a: " + b + " b:" + a);
}
這本來是我上編程課,老師講的一個笑話,說有一個學(xué)生偷懶,直接將 a,b 交換輸出了.
當時覺得那個人真是腦洞大開,而寫代碼應(yīng)該追求簡單明了,這種偷懶,其實也應(yīng)該得到鼓勵和支持.
交換A,B 兩數(shù),從本質(zhì)上來講,是闡述了 A,B 兩數(shù)與結(jié)果之間是一個互換的行為,而具體怎么交換,應(yīng)該有什么步驟, 不同的語言和平臺有不同的方法, 但最終描述的都是這樣的一個本質(zhì).
而函數(shù)式編程,其實就是追求一種<b>"簡單的,高效的,類型之間的關(guān)系"</b>.
這樣的思想更加符合我們?nèi)说乃季S,更加的抽象.
定義:
函數(shù)式編程,實際上是一種編程范式,區(qū)別于命令式注重解決問題的步驟,函數(shù)式注重的數(shù)據(jù)之間的關(guān)系 --- 即 f(x).
從上面的介紹可知,函數(shù)式編程實際上是在寫代碼的時候的一種編程思想,無關(guān)編譯后具體是怎么處理和執(zhí)行的,例如現(xiàn)在非持剀火的 Kotlin, 作為一種函數(shù)式編程語言,可以將它編譯成 class 字節(jié)碼,從編譯后的字節(jié)碼來看,和 Java8 之前的編譯出來的代碼并沒有本質(zhì)的區(qū)別.
函數(shù)式編程的特點
-
代碼簡潔,開發(fā)快速
函數(shù)式編程將大量重復(fù)的的通用操作寫成函數(shù)虚茶,減少了代碼的重復(fù)戈鲁,因此代碼少, 開發(fā)速度快, 而 Kotlin 之所以比 Java 更簡潔, 這也是其中一個重要的原因.
-
更接近自然語言(人類的語言),易于理解
前面的例子,已經(jīng)說明,函數(shù)式編程的思維更接近人類的思維,所以更易被我們理解.
下面會用 Java8 舉例說明 -
更方便的代碼管理,易于"并發(fā)編程"
函數(shù)式編程更為抽象, 更通用的抽象函數(shù)能更加解耦, 模塊化管理更加方便.
無論是 Java 亦或是其他語言,在并發(fā)這塊,代碼編寫和管理都是一個難點.
函數(shù)式編程不依賴嘹叫、也不會改變外界的狀態(tài)荞彼,只要給定輸入?yún)?shù),返回的結(jié)果必定相同待笑。因此鸣皂,每一個函數(shù)都可以被看做獨立單元,也不用擔心死鎖,同步,以及內(nèi)存可見等各種頭疼的多線程問題,這將非常有利于進行單元測試和除錯暮蹂,以及模塊化,解耦等等寞缝。
Java8的函數(shù)式編程
Lambda 表達式
Lambda, 數(shù)學(xué)符號為 λ, 我們上學(xué)時候經(jīng)常使用的符號還有alpha--α, beta--β 等等, 使用 λ 作為函數(shù)式編程的符號,是因為 <b>λ演算</b>:
從理論上說,函數(shù)式語言也不是通過馮諾伊曼體系結(jié)構(gòu)的機器上運行的仰泻,而是通過λ演算來運行的荆陆,就是通過變量替換的方式進行,變量替換為其值或表達式集侯,函數(shù)也替換為其表達式被啼,并根據(jù)運算符進行計算。λ演算是圖靈完全(Turing completeness)的棠枉,但是大多數(shù)情況浓体,函數(shù)式程序還是被編譯成(馮諾依曼機的)機器語言的指令執(zhí)行的。
通俗來說,函數(shù)式思想的本質(zhì)是 λ演算,但這個思想具體到了編程,還是使用機器語言去執(zhí)行的,所以和純粹的函數(shù)式思想有一點點區(qū)別,而不同的函數(shù)式編程語言,在具體的實現(xiàn)上也不盡相同.
Java8 中的 Lambda 表達式 表現(xiàn)為高階函數(shù),即將函數(shù)作為參數(shù)傳入.下面來認識一下 Java8 中的 Lambda 表達式:
//0, lambda 表達式的右邊是一個閉包,應(yīng)該使用{}括起來,這是 lambda 表達式最完整的表達
Consumer<String> consumer3 = (String it) -> {
System.out.println(it);
};
//1, 0 個參數(shù), run() 方法演變成 lambda 表達式
Runnable runnable = () -> System.out.println("123");
//2, 1個參數(shù),傳如 it,假設(shè)這個類型可以推斷出來,則可以省略
Consumer<String> consumer = it -> System.out.println(it);
//3, 指出了 it 的具體類型為 String
Consumer<String> consumer2 = (String it) -> System.out.println(i);
//4, 多條語言,不能省略右邊的大括號
Consumer<String> consumer4 = (String it) -> {//省略大括號編譯不能通過
System.out.println(it);
System.out.println("other code: " + it);
//other code
//
};
//5, 多參數(shù)
BinaryOperator<String> operator = (x, y) -> {
return x + y;
};
//6:方法引用, 0,2,3都可以轉(zhuǎn)為方法引用, 1不行因為輸出了字符串 "123", 4是因為有多條語句
Consumer<String> consumer5 = System.out::println;
Consumer<String> consumer6 = Person::getName;
Consumer<String> consumer7 = Person::new;
類庫
認識了 Lambda 表達式之后,我們來使用它,舉個??
Java 編程中,經(jīng)常使用 CallBack 來進行回調(diào),通常代碼如下:
public static class Person {
private NameChangeListener listener;
public void setListener(NameChangeListener listener) {
this.listener = listener;
}
public static interface NameChangeListener {
void change(String name);
}
}
public static class God {
private NameChangeListener listener;
public void setListener(NameChangeListener listener) {
this.listener = listener;
}
public static interface NameChangeListener {
void change(String name);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setListener(new Person.NameChangeListener() {
@Override
public void change(String name) {
System.out.print("new name: " + name);
}
});
God god = new God();
god.setListener(new God.NameChangeListener() {
@Override
public void change(String name) {
System.out.print("new name: " + name);
}
});
}
上面兩個類, Person 和 God 都有一個回調(diào),且回調(diào)的簽名是一模一樣的,但是不能將 God 的回調(diào)實例傳遞給 Person:
Person person = new Person();
person.setListener(new God.NameChangeListener() {//報錯,編譯無法通過
@Override
public void change(String name) {
System.out.print("new name: " + name);
}
});
盡管兩個回調(diào)的簽名一模一樣的,但是假設(shè)這兩個類是來自不同的類庫, Person 是無法訪問到 God 中的回調(diào)接口,也就無法使用 God 中的回調(diào)替換, 故無法編譯通過.
現(xiàn)在將這兩個回調(diào)統(tǒng)一,抽取出相同的簽名,如下:
interface NameChangeListener {
void change(String name);
}
//更為通用的,假設(shè)參數(shù)不是一個 String, 而是任意的類型,使用泛型可以這么寫
interface Consumer<T> {
void change(T var);
}
上面的 Consumer 這個接口, 只有一個方法,接受一個 T, 返回一個 void, 假設(shè)我們能告訴編譯器,任何符合這樣簽名的,都可以替換掉,那豈不是美滋滋?
<b>Lambda 本質(zhì)上就是將一個通用的函數(shù)接口替換掉和它簽名一模一樣的其他類或接口</b>
使用 Lambda 替換 Person 的匿名內(nèi)部類:
Person person = new Person();
person.setListener(name -> System.out.print("new name: " + name));
根據(jù)之前的 lambda 表達式的介紹,上面這段表達式的是意思是,傳入一個參數(shù),這個參數(shù),從類型推斷上來講,它是 String 類型, 且執(zhí)行了一條打印語言,并且沒有返回值.
這個函數(shù)從簽名上來講,和 Person.NameChangeListener 一模一樣,故在 Java8中允許使用這個 lambda 表達式替換掉前面的匿名內(nèi)部類.
上面這個表達式,其本質(zhì)上是使用 Java8自帶的一個函數(shù)接口,稱之為類庫,我們看一下它的源碼:
可以看到,這個函數(shù)接口, Consumer 和我們上面抽象出來的函數(shù)接口一樣,多了一個默認方法而已,這個默認方法是 java8 接口新增的一個特性,是為了兼容舊接口且保持向后兼容(backward compatibility)提供了途徑.
這里最重要的不是默認方法,而是那個 <b>@FunctionalInterface</b> 注解,表示這個接口是一個函數(shù)接口,可以使用 lambda 表達式,關(guān)于這樣的函數(shù)接口還有很多,都在
package java.util.function;
這個包下面.其實我們在接觸 RxJava 的時候,就已經(jīng)學(xué)到使用到了這些函數(shù)接口,常用的函數(shù)接口非常多,以至于幾乎無法一一記住.
但一般無需刻意去記住這些函數(shù)接口,我們在使用 lambda 表達式的時候無需刻意去想需要使用哪個函數(shù)接口,編譯器會自動匹配. 只有我們在閱讀類庫,或者設(shè)計類庫的時候,可能需要去理解和使用到這些函數(shù)接口.
流
如果是 Java8 中的 lambda 表達式"僅僅"是一種"語法糖",而 Stream 的出現(xiàn)則是 Java 8 的最大亮點.
它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念辈讶。它也不同于 StAX 對 XML 解析的 Stream.
Java 8 中的 Stream 是對集合(Collection)對象功能的增強命浴,它專注于對集合對象進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數(shù)據(jù)操作 (bulk data operation)生闲。Stream API 借助于同樣新出現(xiàn)的 Lambda 表達式媳溺,極大的提高編程效率和程序可讀性。同時它提供串行和并行兩種模式進行匯聚操作碍讯,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢悬蔽,使用 fork/join 并行方式來拆分任務(wù)和加速處理過程。通常編寫并行代碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼捉兴,就可以很方便地寫出高性能的并發(fā)程序屯阀。所以說,Java 8 中首次出現(xiàn)的 java.util.stream 是一個函數(shù)式語言+多核時代綜合影響的產(chǎn)物轴术。
集合都實現(xiàn)了 Collection 接口,在 java8 以后,加上了一個 stream() 的默認方法, 用于使用 流 功能,舉個??,計算集合中的所有大于5的數(shù)字的個數(shù):
/**
* 計算大于5的個數(shù)
*/
public static long addUp(List<Integer> data) {
long count = 0;
for (int i = 0; i < data.size(); i++) {
if (data.get(i) > 5) {
count++;
}
}
return count;
}
/**
* 計算大于5的個數(shù)
*/
public static long addUp8(List<Integer> data) {
return data.stream().filter(it -> it > 5).count();
}
首先會看我們的問題: 計算集合中的所有大于5的數(shù)字的個數(shù)
按照數(shù)學(xué)思維,我們應(yīng)該先將所有大于5的數(shù)字找出來,然后算一共有幾個.
再看上述流的例子,首先 data 通過 stream() 獲得到了集合的流,然后通過 filter() 方法篩選出了大于5的數(shù)字(其中篩選代碼使用了 lambda 表達式 表達),最后通過 count() 方法計數(shù).
根據(jù)函數(shù)式編程的思想,流符合對簡潔的要求,符合人的思維.
Stream 接口中定義了很多 filter, count 等方法, 這些方法的參數(shù)都是前文中所提到到 <b>函數(shù)式接口</b> 風(fēng)格. 這些方法可以按照兩個種類來區(qū)分: 惰性求值和及時求值.
上文中, 假設(shè)將流的代碼改為下面的例子:
/**
* 計算大于5的個數(shù)
*/
public static long addUp8(List<Integer> data) {
Stream<Integer> stream = data.stream().filter(it -> {
System.out.println("456");
return it > 5;
});
System.out.println("123");
return stream.count();
}
public static void main(String[] args) {
List<Integer> data = new ArrayList<>();
for (int i = 0; i < 100; i++) {
data.add(i);
}
addUp8(data);
}
結(jié)果:
123
456
456
.......
從結(jié)果中可以看到, 456的代碼盡管是在 123的之前,但是因為 filter() 屬于惰性求值,只有真正的執(zhí)行到它的時候它才會運行.
<b>區(qū)分 Stream 接中的方法是及時求值,還是惰性求值,只要看它的返回值,如果不是一個 Stream, 那就是及時求值.</b>
對 RxJava 比較熟悉的同學(xué)就會發(fā)現(xiàn)了, 流和 RxJava 很像,實際上他們的操作符基本上都是一樣的,編程思想也是類似,都可以定義為一種函數(shù)式編程.
Stream 是為構(gòu)建內(nèi)存中集合的計算流程而設(shè)計的,而 RxJava 則是為了組合異步和基于事件的系統(tǒng)流程而設(shè)計的. Stream 沒有取數(shù)據(jù),而是把數(shù)據(jù)放進去, Stream 是為了計算最終的結(jié)果,而 RxJava 在線程模型上則像是 CompletableFuture, 為了得到一個響應(yīng)的結(jié)果,故RxJava 也叫響應(yīng)式編程.
數(shù)據(jù)并行化
上文流的例子,很好的體現(xiàn)了函數(shù)式編程中的簡潔,接近人的思維,現(xiàn)在介紹易于編寫并發(fā)這塊,下文集合串行并行對比:
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
integerList.add(i);
}
//串行
long b = System.currentTimeMillis();
System.out.println("start...");
long a = integerList.stream().map(it -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return it + 1;
}).count();
System.out.println("end: " + (System.currentTimeMillis() - b));
//并行,關(guān)鍵字: parallelStream()
long c = System.currentTimeMillis();
System.out.println("\n\n");
System.out.println("start...");
long d = integerList.parallelStream().map(it -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return it + 1;
}).count();
System.out.println("end: " + (System.currentTimeMillis() - c));
}
結(jié)果:
start...
end: 6124
start...
end: 1513
就這么簡單, Java8 的流使用 parallelStream()方法能很方便的進行并行操作,其本質(zhì)上是使用Fork/Join框架實現(xiàn)的(詳情可見), 這個框架自 Java7 引入,能將一個大任務(wù)切割成若干個小任務(wù)并行執(zhí)行
Java8 數(shù)組也有并行化操作,例如我們經(jīng)常使用的數(shù)組初始化:
public static void main(String[] args) {
long a = System.currentTimeMillis();
System.out.print("\n\nstart...");
String[] strings = new String[20];
for (int i = 0; i < strings.length; i++) {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
strings[i] = i + "";
}
System.out.print("end: " + (System.currentTimeMillis() - a));
long b = System.currentTimeMillis();
System.out.print("\n\nstart...");
String[] strings2 = new String[20];
Arrays.parallelSetAll(strings2, i -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return i + "";
});
System.out.print("end: " + (System.currentTimeMillis() - b));
}
結(jié)果:
start...end: 6059
start...end: 1562
使用 Java8 函數(shù)式編程注意事項
Java 作為一門語言,在很多方面都經(jīng)過了時間的考驗.問題在于,這么多年來, Java 并沒有緊跟時代向前演進, 落了一個保守的名聲,而 Java8的出現(xiàn)則是一個積極的型號,相對于6,7而言,它正在大步前進,同時它的改進也遇到了一些問題.
lambda 表達式可引用外部變量
根據(jù)之前對函數(shù)式思想的介紹, 函數(shù)式是不修改外部變量,這樣更有利于它的編程和測試,更純粹和簡潔,但Java8 中, lambda 表達式還是可以引用外部變量:
//全局變量
String a;
public void testLambda() {
Consumer<String> consumer = it -> {
a = it;
};
}
public void testLambda2() {
String b = "123";
Consumer<String> consumer = it -> {
System.out.println(b);
};
//如果修改 b 則無法編譯通過
// b = "456";
}
可以看到 lambda 表達是仍然可以引用全局變量,這對函數(shù)式思想來說,不是一個好習(xí)慣,所以我們在編寫代碼的時候要避免使用這種情況.
在局部變量的時候,要求這個變量必須是final,或是一個既定的 final 值,即沒有地方會修改到它.
上面例子中,假設(shè)不注釋掉 b = "456", 則編譯無法通過, 因為 Java8的設(shè)計是函數(shù)式編程,lambda 是f(X)函數(shù)關(guān)系,它是對值求關(guān)系,不是對變量求關(guān)系,假設(shè)變量是final,或是一個既定的 final 值,則 表達式引用的是一個值,不是具體的某個變量.
并發(fā)編程并不總是最優(yōu)的,避免并發(fā)陷阱
這兩個并行的例子都使用了線程等待,模擬了一些耗時操作的情形
實際上假設(shè)去掉等待時間, 并行的速度未必會快于串行,這取決于硬件環(huán)境和任務(wù)本身等多重因素
以上面的為例,假設(shè)去掉了等待,在我的電腦上執(zhí)行結(jié)果為:
public static void main(String[] args) {
long a = System.currentTimeMillis();
System.out.print("\n\nstart...");
String[] strings = new String[20];
for (int i = 0; i < strings.length; i++) {
// try {
// TimeUnit.MILLISECONDS.sleep(300);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
strings[i] = i + "";
}
System.out.print("end: " + (System.currentTimeMillis() - a));
long b = System.currentTimeMillis();
System.out.print("\n\nstart...");
String[] strings2 = new String[20];
Arrays.parallelSetAll(strings2, i -> {
// try {
// TimeUnit.MILLISECONDS.sleep(300);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return i + "";
});
System.out.print("end: " + (System.currentTimeMillis() - b));
}
結(jié)果:
start...end: 0
start...end: 66
可以看到初始化一個20長度的數(shù)組,在串行里幾乎不耗費任何時間,反而并行因為需要上下文切換和任務(wù)調(diào)度,消耗了一定的時間.
<b>并行雖好用,也要根據(jù)情況正確的使用.</b>
總結(jié)
本次主題主要介紹什么是函數(shù)式編程,其主要特點, 以及它在 Java8 中是怎么體現(xiàn)的.
計算機發(fā)展至今不到百年,如今更掀起一次又一次的技術(shù)革命,而作為與機器打交道的編程語言更是層出不窮, 作為個體程序員很難一一去學(xué)習(xí)它們, 我們更應(yīng)該去思考編程語言的設(shè)計思想.
引用開頭摘自書中的一段話:
學(xué)習(xí)一種全新的語言,困難不在數(shù)據(jù)類型,控制語句,或數(shù)據(jù)結(jié)構(gòu)----語法不過是些小細節(jié)罷了难衰。
真正應(yīng)該學(xué)習(xí)的是:怎么學(xué)會用另一種方式去思考,去學(xué)習(xí)語言的設(shè)計思想.
函數(shù)式編程是一種編程范式,區(qū)別于命令式注重解決問題的步驟,函數(shù)式注重的數(shù)據(jù)之間的關(guān)系 --- 即 f(x).
它不是某一個語言或者某一種語言特有的,如我們熟悉的 RxJava 就包含了函數(shù)式編程思想,所以 RxJava 也叫函數(shù)式響應(yīng)式編程. 而比較火的 Kotlin, 也可以是面向?qū)ο蟮?也可以是函數(shù)式編程風(fēng)格的.
語言本身只是函數(shù)式思想的具體表達,而我們更應(yīng)該轉(zhuǎn)換的是編寫代碼時的思考方式.