## 高階函數
我還記得在了解到FP以上的各種好處后想到:“這些優勢都很吸引人,可是,如果必須非要用這種所有變量都是final的蹩腳語言,估計還是不怎么實用吧”。其實這樣的想法是不對的。對于Java這樣的指令式語言來說,如果所有的變量都是必須是final的,那么確實很束手束腳。然而對函數式語言來說,情況就不一樣了。函數式語言提供了一種特別的抽象工具,這種工具將幫助使用者編寫FP代碼,讓他們甚至都沒想到要修改變量的值。高階函數就是這種工具之一。
FP語言中的函數有別于Java或是C。可以說這種函數是一個[全集](http://zh.wikipedia.org/wiki/%E5%85%A8%E9%9B%86):Java函數可以做到的它都能做,同時它還有更多的能力。首先,像在C里寫程序那樣創建一個函數:
~~~
int add(int i, int j) {
return i + j;
}
~~~
看起來和C程序沒什么區別,但是很快你就可以看出區別來。接下來我們擴展Java的編譯器以便支持這種代碼,也就是說,當我們寫下以上的程序編譯器會把它轉化成下面的Java程序(別忘了,所有的變量都是final的):
~~~
class add_function_t {
int add(int i, int j) {
return i + j;
}
}
add_function_t add = new add_function_t();
~~~
在這里,符號add并不是一個函數,它是只有一個函數作為其成員的簡單的類。這樣做有很多好處,可以在程序中把add當成參數傳給其他的函數,也可以把add賦給另外一個符號,還可以在運行時創建add_function_t的實例然后在不再需要這些實例的時候由系統回收機制處理掉。這樣做使得函數成為和integer或是string這樣的[第一類對象](http://zh.wikipedia.org/zh/%E7%AC%AC%E4%B8%80%E9%A1%9E%E7%89%A9%E4%BB%B6)。對其他函數進行操作(比如說把這些函數當成參數)的函數,就是所謂的高階函數。別讓這個看似高深的名字嚇倒你(譯者:好死不死起個這個名字,初一看還準備搬出已經塵封的高數教材……),它和Java中操作其他類(也就是把一個類實例傳給另外的類)的類沒有什么區別。可以稱這樣的類為“高階類”,但是沒人會在意,因為Java圈里就沒有什么很強的學術社團。(譯者:這是高級黑嗎?)
那么什么時候該用高階函數,又怎樣用呢?我很高興有人問這個問題。設想一下,你寫了一大堆程序而不考慮什么類結構設計,然后發現有一部分代碼重復了幾次,于是你就會把這部分代碼獨立出來作為一個函數以便多次調用(所幸學校里至少會教這個)。如果你發現這個函數里有一部分邏輯需要在不同的情況下實現不同的行為,那么你可以把這部分邏輯獨立出來作為一個高階函數。搞暈了?下面來看看我工作中的一個真實的例子。
假設有一段Java的客戶端程序用來接收消息,用各種方式對消息做轉換,然后發給一個服務器。
~~~
class MessageHandler {
void handleMessage(Message msg) {
// ...
msg.setClientCode("ABCD_123");
// ...
sendMessage(msg);
}
// ...
}
~~~
再進一步假設,整個系統改變了,現在需要發給兩個服務器而不再是一個了。系統其他部分都不變,唯獨客戶端的代碼需要改變:額外的那個服務器需要用另外一種格式發送消息。應該如何處理這種情況呢?我們可以先檢查一下消息要發送到哪里,然后選擇相應的格式把這個消息發出去:
~~~
class MessageHandler {
void handleMessage(Message msg) {
// ...
if(msg.getDestination().equals("server1") {
msg.setClientCode("ABCD_123");
} else {
msg.setClientCode("123_ABC");
}
// ...
sendMessage(msg);
}
// ...
}
~~~
可是這樣的實現是不具備擴展性的。如果將來需要增加更多的服務器,上面函數的大小將呈線性增長,使得維護這個函數最終變成一場噩夢。面向對象的編程方法告訴我們,可以把MessageHandler變成一個基類,然后將針對不同格式的消息編寫相應的子類。
~~~
abstract class MessageHandler {
void handleMessage(Message msg) {
// ...
msg.setClientCode(getClientCode());
// ...
sendMessage(msg);
}
abstract String getClientCode();
// ...
}
class MessageHandlerOne extends MessageHandler {
String getClientCode() {
return "ABCD_123";
}
}
class MessageHandlerTwo extends MessageHandler {
String getClientCode() {
return "123_ABCD";
}
}
~~~
這樣一來就可以為每一個接收消息的服務器生成一個相應的類對象,添加服務器就變得更加容易維護了。可是,這一個簡單的改動引出了很多的代碼。僅僅是為了支持不同的客戶端行為代碼,就要定義兩種新的類型!現在來試試用我們剛才改造的語言來做同樣的事情,注意,這種語言支持高階函數:
~~~
class MessageHandler {
void handleMessage(Message msg, Function getClientCode) {
// ...
Message msg1 = msg.setClientCode(getClientCode());
// ...
sendMessage(msg1);
}
// ...
}
String getClientCodeOne() {
return "ABCD_123";
}
String getClientCodeTwo() {
return "123_ABCD";
}
MessageHandler handler = new MessageHandler();
handler.handleMessage(someMsg, getClientCodeOne);
~~~
在上面的程序里,我們沒有創建任何新的類型或是多層類的結構。僅僅是把相應的函數作為參數進行傳遞,就做到了和用面向對象編程一樣的事情,而且還有額外的好處:一是不再受限于多層類的結構。這樣做可以做運行時傳遞新的函數,可以在任何時候改變這些函數,而且這些改變不僅更加精準而且觸碰的代碼更少。這種情況下編譯器其實就是在替我們編寫面向對象的“粘合”代碼(譯者:又稱膠水代碼,粘接代碼)!除此之外我們還可以享用FP編程的其他所有優勢。函數式編程能提供的抽象服務還遠不止于此。高階函數只不過是個開始。