## 惰性求值
惰性求值(或是延遲求值)是一種有趣的技術,而當我們投入函數式編程的懷抱后這種技術就有了得以實現的可能。前面介紹并發執行的時候已經提到過如下代碼:
~~~
String s1 = somewhatLongOperation1();
String s2 = somewhatLongOperation2();
String s3 = concatenate(s1, s2);
~~~
在指令式語言中以上代碼執行的順序是顯而易見的。由于每個函數都有可能改動或者依賴于其外部的狀態,因此必須順序執行。先是計算somewhatLongOperation1,然后到somewhatLongOperation2,最后執行concatenate。函數式語言就不一樣了。
在前面討論過,somewhatLongOperation1和somewhatLongOperation2是可以并發執行的,因為函數式語言保證了一點:沒有函數會影響或者依賴于全局狀態。可是萬一我們不想要這兩個函數并發執行呢?這種情況下是不是也還是要順序執行這些函數?答案是否定的。只有到了執行需要s1、s2作為參數的函數的時候,才真正需要執行這兩個函數。于是在concatenate這個函數沒有執行之前,都沒有需要去執行這兩個函數:這些函數的執行可以一直推遲到concatenate()中需要用到s1和s2的時候。假如把concatenate換成另外一個函數,這個函數中有條件判斷語句而且實際上只會需要兩個參數中的其中一個,那么就完全沒有必要執行計算另外一個參數的函數了!Haskell語言就是一個支持惰性求值的例子。Haskell不能保證任何語句會順序執行(甚至完全不會執行到),因為Haskell的代碼只有在需要的時候才會被執行到。
除了這些優點,惰性求值也有缺點。這里介紹了它的優點,我們將在下一章節介紹這些缺點以及如何克服它們。
### 代碼優化
惰性求值使得代碼具備了巨大的優化潛能。支持惰性求值的編譯器會像數學家看待代數表達式那樣看待函數式程序:抵消相同項從而避免執行無謂的代碼,安排代碼執行順序從而實現更高的執行效率甚至是減少錯誤。在此基礎上優化是不會破壞代碼正常運行的。嚴格使用形式系統的基本元素進行編程帶來的最大的好處,是可以用數學方法分析處理代碼,因為這樣的程序是完全符合數學法則的。
### 抽象化控制結構
惰性求值技術提供了更高階的抽象能力,這提供了實現程序設計獨特的方法。比如說下面的控制結構:
~~~
unless(stock.isEuropean()) {
sendToSEC(stock);
}
~~~
程序中只有在stock為European的時候才執行sendToSEC。如何實現例子中的unless?如果沒有惰性求值就需要求助于某種形式的宏(譯者:用if不行么?),不過在像Haskell這樣的語言中就不需要那么麻煩了。直接實現一個unless函數就可以!
~~~
void unless(boolean condition, List code) {
if(!condition)
code;
}
~~~
請注意,如果condition值為真,那就不會計算code。在其他嚴格語言(見[嚴格求值](http://zh.wikipedia.org/wiki/%E6%B1%82%E5%80%BC%E7%AD%96%E7%95%A5#.E4.B8.A5.E6.A0.BC.E6.B1.82.E5.80.BC_.28Strict_evaluation.29))中這種行為是做不到的,因為在進入unless這個函數之前,作為參數的code已經被計算過了。
### 無窮數據結構
惰性求值技術允許定義無窮數據結構,這要在嚴格語言中實現將非常復雜。例如一個儲存Fibonacci數列數字的列表。很明顯這樣一個列表是無法在有限的時間內計算出這個無窮的數列并存儲在內存中的。在像Java這樣的嚴格語言中,可以定義一個Fibonacci函數,返回這個序列中的某個數。而在Haskell或是類似的語言中,可以把這個函數進一步抽象化并定義一個Fibonacci數列的無窮列表結構。由于語言本身支持惰性求值,這個列表中只有真正會被用到的數才會被計算出來。這讓我們可以把很多問題抽象化,然后在更高的層面上解決它們(比如可以在一個列表處理函數中處理無窮多數據的列表)。
### 不足之處
俗話說天下沒有免費的午餐?。惰性求值當然也有其缺點。其中最大的一個就是,嗯,惰性。現實世界中很多問題還是需要嚴格求值的。比如說下面的例子:
~~~
System.out.println("Please enter your name: ");
System.in.readLine();
~~~
在惰性語言中沒人能保證第一行會中第二行之前執行!這也就意味著我們不能處理IO,不能調用系統函數做任何有用的事情(這些函數需要按照順序執行,因為它們依賴于外部狀態),也就是說不能和外界交互了!如果在代碼中引入支持順序執行的代碼原語,那么我們就失去了用數學方式分析處理代碼的優勢(而這也意味著失去了函數式編程的所有優勢)。幸運的是我們還不算一無所有。數學家們研究了不同的方法用以保證代碼按一定的順序執行(in a functional setting?)。這一來我們就可以同時利用到函數式和指令式編程的優點了!這些方法有continuations,monads以及uniqueness typing。這篇文章僅僅介紹了continuations,以后再討論monads和uniqueness typing。有意思的是呢,coutinuations處理強制代碼以特定順序執行之外還有其他很多出處,這些我們在后面也會提及。