Functional Programming
阿新 • • 發佈:2018-11-07
1.什麼是函數語言程式設計
函數語言程式設計是通過複合春函式來構建軟體的過程,它避免了共享的狀態,易變的資料以及副作用。函數語言程式設計是宣告式
而不是命令式
,並且應用程式狀態通過純函式流轉。對比面向物件程式設計,後者的應用程式狀態是共享並共用於物件方法。
函數語言程式設計的核心概念:
- 純函式
- 函式複合
- 避免共享狀態
- 避免改變狀態
- 避免副作用
純函式
給它同樣的輸入,總是返回同樣的結果,並且沒有副作用
函式複合
函式複合是結合兩個或者多個函式,從而產生一個新函式或進行某些計算的過程。例如,複合操作f·g在javasrcipt中相當於執行f(g(x))。
共享狀態
- 共享狀態是任意變數,物件或者記憶體空間存在於共享作用域下,或者作為物件的屬性在各個作用域之間被傳遞。共享作用域包括全域性作用域和閉包作用域。通常,在面向物件程式設計中,物件以新增屬性到其他物件上的方式在作用域之間共享。
- 舉個例子,一個電腦遊戲可能會控制一個遊戲物件(game object),它上面有角色(characters)和遊戲道具(items),這些資料作為屬性儲存在遊戲物件之上。而函數語言程式設計避免共享狀態 —— 與前者不同地,
它依賴於不可變資料結構和純粹的計算過程來從已存在的資料中派生出新的資料
。- 共享狀態的問題是為了理解函式的作用,你需要了解那個函式所用到的全部共享變數的變化歷史。
- 共享狀態的另一個常見問題是改變函式呼叫次序可能導致一連串的錯誤,因為函式操作共享資料是依時序的
//使用共享資料,函式呼叫的次序會改變函式呼叫的結果
const x = {
val: 2
};
const x1 = () => x.val += 1;
const x2 = () => x.val *= 2;
x1();
x2();
console.log(x.val); // 6
//下面的例子與上面的相同,除了……
const y = {
val: 2
};
const y1 = () => y.val += 1;
const y2 = () => y.val *= 2;
// ...函式的呼叫次序顛倒了一下...
y2();
y1();
// ... 這改變了結果值:
console.log(y.val); // 5
不可變性
- 一個不可變的物件是指一個物件不會再它建立之後被改變,對應的,一個可變物件是指任何在建立之後可以被改變的物件
- 不要混淆const和不可變性,const 建立一個變數繫結,讓該變數不能再次被賦值, const 並不建立不可變物件。雖然不能改變繫結到這個變數名上的物件,但你仍然可以改變它的屬性,所以說const的變數仍然是可變的,而不是不可變的. 不可變的物件完全不能被改變,可以通過深度凍結物件來創造一個真正不可變的值
const a = Object.freeze({
foo: 'Hello',
bar: 'world',
baz: '!'
});
a.foo = 'Goodbye';
// Error: Cannot assign to read only property 'foo' of object Object
然而凍結的物件只是表面一層不可變,例如,深層的屬性還是可以被改變:
const a = Object.freeze({
foo: { greeting: 'Hello' },
bar: 'world',
baz: '!'
});
a.foo.greeting = 'Goodbye';
console.log(`${ a.foo.greeting }, ${ a.bar }${a.baz}`);
副作用
副作用是值除了函式返回值以外,任何在函式呼叫之外觀察到的應用程式狀態改變,比如:
- 改變任何外部變數或物件屬性(全域性變數,或者在父級函式作用域臉上的變數)
- 寫日誌
- 在螢幕輸出
- 寫檔案
- 髮網絡請求
- 觸發任何外部程序
- 呼叫另外一個有副作用的函式
2.通過高階函式提高可重用性
在js中,函式是一等公民
, js允許使用者將函式作為資料,可以將他們賦值給變數,作為引數傳遞給其他函式,將他們作為返回值返回等
高階函式是指以函式為引數或者以函式為返回值,或者既以函式為引數又以函式為返回值。
const double = n => n * 2;
const doubleMap = numbers => numbers.map(double);
console.log(doubleMap([2, 3, 4])); // [ 4, 6, 8 ]
3.對比生命式與命令式
-
函數語言程式設計是一個宣告式正規化,意思是說程式邏輯不需要通過明確描述控制流程來表達。
-
命令式 程式花費大量程式碼來描述用來達成期望結果的特定步驟 —— 控制流:即如何做。
-
宣告式 程式抽象了控制流過程,花費大量程式碼描述的是資料流:即做什麼。
命令式:
const doubleMap = numbers => {
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
return doubled;
};
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
宣告式:
const doubleMap = numbers => numbers.map(n => n * 2);
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
命令式 程式碼中頻繁使用語句。語句是指一小段程式碼,它用來完成某個行為。通用的語句例子包括 for、if、switch、throw,等等……
宣告式 程式碼更多依賴表示式。表示式是指一小段程式碼,它用來計算某個值。表示式通常是某些函式呼叫的複合、一些值和操作符,用來計算出結果值。
4.結論
- 使用純函式而不是使用共享狀態和副作用
- 讓可變資料成為不可變的
- 用函式複合替代命令控制流
- 使用高階函式來操作許多資料型別,建立通用、可複用功能取代只是操作集中的資料的方法。
- 使用宣告式而不是命令式程式碼(關注做什麼,而不是如何做)
- 使用表示式替代語句
- 使用容器與高階函式替代多型