注:本文轉載自外刊IT評論。
上週末,有人問我,如何學會函數語言程式設計。我的回答是:用你現在使用的程式語言寫純正函式。
純函式唯一的輸入是它的引數列表,唯一的輸出是它的返回值。如果你以前從未接觸過這個概念,你會以為所有的函式都是純正的。畢竟,所有的函式都是接受一個或多個輸入值,返回一個輸出值。但在某些傳統程式設計中,經常會有一些外來的資訊流入或流出函式。例如,一個不規範的函式有可能會依賴一個全域性變數或一些類成員資料。在這種情況下,函式的行為並不完全決定於它的引數值。相似的,一個不規範的函式有可能會更改一個全域性變數或修改資料庫。這種情況下,函式除了返回值外,還會附帶一些額外操作。
你可以用任何語言寫出純函式,只是有些語言容易寫,有些語言寫起來比較複雜。例如,沒有人會把Fortran當作一種函式式語言,但有些人(M. J. D. Powell)卻強制自己在Fortran裡要寫純函式。
為什麼要寫純函式?純函式具有親系透徹性(referential transparency),也就是說,針對相同的輸入值,它一定給出相同的輸出值。函式輸出不依賴系統時間、資料庫狀態以及任何沒有顯式的作為引數傳入函式的東西。這也表明純函式易於理解(因此也易於除錯和測試)。
你可以一直使用純函式。但如果你想把一個值放到資料庫裡,光通過純函式是實現不了的。或者當你想呼叫一個隨機數發生器時,你可不想它保持親系透徹性 —每次都返回相同的值。但是,在可以用到純函式的時候,你應該使用純函式,用純函式來消除越界聯絡。完全的純函式程式是不現實的;有人建議說最佳的純度係數應該是 85% 。
那麼,為什麼程式設計師不大量的使用純函式呢?一個原因是,純函式需要更長的參數列。在物件導向的程式語言裡,物件可以隱式的依賴物件狀態來減少引數數量。對於這更簡潔的方法介面,你付出的代價是,你無法只通過方法本身來理解這個方法。呼叫這個方法時你還需要知道物件的狀態。為了獲得更短的方法介面而放棄親系透徹性值不值得?這依賴於你的上下文環境和你的風格,按我的觀點,我更願意用更長的函式介面來換取更純的函式。
另外一個人們不太喜歡使用純函式的原因是,把大型資料結構傳入函式太麻煩。但這也依賴於你怎麼幹。你可以只是形式上的把一個物件傳輸函式,而不是把整個物件按位元組拷貝進去。
為了效率,你也可以製造一些假純度。例如,Mike Swaim最近在一個評論裡給出了一個如何利用Memoization讓程式的速度提升數個等級的例子。(Memoization是一種快取技術。當一個函式向系統請求計算某些東西時,它首先看看這個東西是否已經被快取過。如果是,它會從從快取裡取出結果返回。如果否,它會計算它,然後把輸出放到快取裡。)使用Memoization技術的函式嚴格的說不是純函式—它的計算操作直接受快取狀態的影響—但這樣的函式仍然保持親系透徹性,如果你給它相同的輸入,它總會產生相同的輸出。你可以認為稱這樣的函式為純函式是一種欺騙,的確也是,但如果你總是糾結於這種事情,那你也知道,完全純函式是有副作用的。