不用任何賦值的程式設計稱為*函式式*程式設計
SICP 第三章的標題是:模組化、物件和狀態。我在這一章找到了「函式式程式設計」的定義(見知識點五),哈哈真是一本神書。
知識點一:
開篇的一段話十分吸引我,這段話在高層次上說明了面向物件程式設計的缺點,以及 Rx.js 這種程式設計正規化的優點。
有一種非常強有力的設計策略,特別適合於構造那些模擬真實物理系統的程式,那就是「基於被模擬的系統的結構去設計程式的結構」。
在這一章裡,我們要研究兩種特點很鮮明的組織策略,它們源自對於系統結構的兩種非常不同的世界觀。第一種策略將注意力集中在「物件」身上,將一個大型系統看成一大批物件,他們的行為可能隨著時間的進展而不斷變化。第二種策略將注意力集中在流過系統的資訊流上,非常像電子工程師觀察一個訊號處理系統。
基於物件的途徑和基於流處理的途徑,都對程式設計提出了具有重要意義的語言要求。
對於物件途徑而言,我們必須關注計算物件可以怎樣變化而又同時保持其標識。這將迫使我們拋棄老的計算的代換模型,轉向更機械式的、理論上也更不容易把握的環境模型。在處理物件、變化和標識時,各種困難的根源都在於我們需要在這一計算模型中與時間搏鬥。如果允許程式併發執行的話,事情就會變得更困難。
對於流方式來說,它特別能夠用於鬆解在我們的模型中對時間的模擬和計算機求值過程中的各種事件的發生順序。我們將通過延時求值 做到這一點。
知識點二:物件是有狀態的
考慮一個取錢的函式 withdraw
// 初始金額 100 > withdraw(25) < 75 // 餘額 75 > withdraw(25) < 50 > withdraw(25) < Error: 餘額不足 > withdraw(15) < 35 複製程式碼
同樣一個函式,每次執行的結果卻不一樣。第一章的代換模型不再有用了。
withdraw 應該如何用「過程」實現呢?記得嗎,之前我們說過也許資料結構都可以用「過程」實現。
let withdraw = (() => { let balance = 100 return (amount) => { if(amount <= balance){ balance = balance - amount return balance }else{ throw new Error('餘額不足') } } })() 複製程式碼
這一句 balance = balance - amount 是本書首次出現的對一個量進行賦值的語句(這裡 let balance = 100 是「初始化」不是「賦值」, balance 的第二次賦值才是「賦值」)。
Scheme 語言裡賦值的語法是
set! balance (- balance amount) 複製程式碼
賦值被設計成 set! ,足見 Scheme 對賦值的厭惡。
知識點三:兩個物件的狀態是互相獨立的
我們用 makeWithdraw 來建立兩個 withdraw,會發現它們兩個的狀態是互不相干的:
let makeWithdraw = (balance) => (amount) => { if(amount < balance){ balance = balance - amount return balance }else{ throw new Error('餘額不足') } } 複製程式碼
下面是兩個 withdraw 的行為:
let withdraw1 = makeWithdraw(100) let withdraw2 = makeWithdraw(100) withdraw1(50) // 50 withdraw2(70) // 30 複製程式碼
知識點四:訊息傳遞風格
使用訊息傳遞風格就可以構造 account 物件了,account 物件可以響應 withdraw(取錢)和 deposit(存錢)訊息:
let makeAccount = balance => { let withdraw = (amount) => { if(amount < balance){ balance = balance - amount return balance }else{ throw new Error('餘額不足') } } let deposit = (amount) => { balance = balance + amount return balance } let dispatch = (m) => { return ( m === 'withdraw' ? withdraw : m === 'deposit' ? deposit : new Error('unknown request')) } return dispatch } 複製程式碼
接下來是使用 makeAccount 創造兩個 account 物件(其實是過程):
let account1 = makeAccount(100) account1('withdraw')(70) // 30 account1('deposit')(50) // 80 寫成 Scheme 其實更像是訊息傳遞 ((account1 'withdraw) 50) ((account1 'deposit ) 50) 複製程式碼