高階函數(shù)

2018-02-24 15:53 更新

高階函數(shù)

我還記得在了解到FP以上的各種好處后想到:“這些優(yōu)勢都很吸引人,可是,如果必須非要用這種所有變量都是final的蹩腳語言,估計(jì)還是不怎么實(shí)用吧”。其實(shí)這樣的想法是不對的。對于Java這樣的指令式語言來說,如果所有的變量都是必須是final的,那么確實(shí)很束手束腳。然而對函數(shù)式語言來說,情況就不一樣了。函數(shù)式語言提供了一種特別的抽象工具,這種工具將幫助使用者編寫FP代碼,讓他們甚至都沒想到要修改變量的值。高階函數(shù)就是這種工具之一。
FP語言中的函數(shù)有別于Java或是C??梢哉f這種函數(shù)是一個全集:Java函數(shù)可以做到的它都能做,同時(shí)它還有更多的能力。首先,像在C里寫程序那樣創(chuàng)建一個函數(shù):

int add(int i, int j) {
    return i + j;
}

看起來和C程序沒什么區(qū)別,但是很快你就可以看出區(qū)別來。接下來我們擴(kuò)展Java的編譯器以便支持這種代碼,也就是說,當(dāng)我們寫下以上的程序編譯器會把它轉(zhuǎn)化成下面的Java程序(別忘了,所有的變量都是final的):

class add_function_t {
    int add(int i, int j) {
        return i + j;
    }
}

add_function_t add = new add_function_t();

在這里,符號add并不是一個函數(shù),它是只有一個函數(shù)作為其成員的簡單的類。這樣做有很多好處,可以在程序中把a(bǔ)dd當(dāng)成參數(shù)傳給其他的函數(shù),也可以把a(bǔ)dd賦給另外一個符號,還可以在運(yùn)行時(shí)創(chuàng)建add_function_t的實(shí)例然后在不再需要這些實(shí)例的時(shí)候由系統(tǒng)回收機(jī)制處理掉。這樣做使得函數(shù)成為和integer或是string這樣的第一類對象。對其他函數(shù)進(jìn)行操作(比如說把這些函數(shù)當(dāng)成參數(shù))的函數(shù),就是所謂的高階函數(shù)。別讓這個看似高深的名字嚇倒你(譯者:好死不死起個這個名字,初一看還準(zhǔn)備搬出已經(jīng)塵封的高數(shù)教材……),它和Java中操作其他類(也就是把一個類實(shí)例傳給另外的類)的類沒有什么區(qū)別??梢苑Q這樣的類為“高階類”,但是沒人會在意,因?yàn)镴ava圈里就沒有什么很強(qiáng)的學(xué)術(shù)社團(tuán)。(譯者:這是高級黑嗎?)

那么什么時(shí)候該用高階函數(shù),又怎樣用呢?我很高興有人問這個問題。設(shè)想一下,你寫了一大堆程序而不考慮什么類結(jié)構(gòu)設(shè)計(jì),然后發(fā)現(xiàn)有一部分代碼重復(fù)了幾次,于是你就會把這部分代碼獨(dú)立出來作為一個函數(shù)以便多次調(diào)用(所幸學(xué)校里至少會教這個)。如果你發(fā)現(xiàn)這個函數(shù)里有一部分邏輯需要在不同的情況下實(shí)現(xiàn)不同的行為,那么你可以把這部分邏輯獨(dú)立出來作為一個高階函數(shù)。搞暈了?下面來看看我工作中的一個真實(shí)的例子。

假設(shè)有一段Java的客戶端程序用來接收消息,用各種方式對消息做轉(zhuǎn)換,然后發(fā)給一個服務(wù)器。

class MessageHandler {
    void handleMessage(Message msg) {
        // ...
        msg.setClientCode("ABCD_123");
        // ...

        sendMessage(msg);
    }

    // ...
}

再進(jìn)一步假設(shè),整個系統(tǒng)改變了,現(xiàn)在需要發(fā)給兩個服務(wù)器而不再是一個了。系統(tǒng)其他部分都不變,唯獨(dú)客戶端的代碼需要改變:額外的那個服務(wù)器需要用另外一種格式發(fā)送消息。應(yīng)該如何處理這種情況呢?我們可以先檢查一下消息要發(fā)送到哪里,然后選擇相應(yīng)的格式把這個消息發(fā)出去:

class MessageHandler {
    void handleMessage(Message msg) {
        // ...
        if(msg.getDestination().equals("server1") {
            msg.setClientCode("ABCD_123");
        } else {
            msg.setClientCode("123_ABC");
        }
        // ...

        sendMessage(msg);
    }

    // ...
}

可是這樣的實(shí)現(xiàn)是不具備擴(kuò)展性的。如果將來需要增加更多的服務(wù)器,上面函數(shù)的大小將呈線性增長,使得維護(hù)這個函數(shù)最終變成一場噩夢。面向?qū)ο蟮木幊谭椒ǜ嬖V我們,可以把MessageHandler變成一個基類,然后將針對不同格式的消息編寫相應(yīng)的子類。

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";
    }
}

這樣一來就可以為每一個接收消息的服務(wù)器生成一個相應(yīng)的類對象,添加服務(wù)器就變得更加容易維護(hù)了??墒?,這一個簡單的改動引出了很多的代碼。僅僅是為了支持不同的客戶端行為代碼,就要定義兩種新的類型!現(xiàn)在來試試用我們剛才改造的語言來做同樣的事情,注意,這種語言支持高階函數(shù):

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);

在上面的程序里,我們沒有創(chuàng)建任何新的類型或是多層類的結(jié)構(gòu)。僅僅是把相應(yīng)的函數(shù)作為參數(shù)進(jìn)行傳遞,就做到了和用面向?qū)ο缶幊桃粯拥氖虑?,而且還有額外的好處:一是不再受限于多層類的結(jié)構(gòu)。這樣做可以做運(yùn)行時(shí)傳遞新的函數(shù),可以在任何時(shí)候改變這些函數(shù),而且這些改變不僅更加精準(zhǔn)而且觸碰的代碼更少。這種情況下編譯器其實(shí)就是在替我們編寫面向?qū)ο蟮摹罢澈稀贝a(譯者:又稱膠水代碼,粘接代碼)!除此之外我們還可以享用FP編程的其他所有優(yōu)勢。函數(shù)式編程能提供的抽象服務(wù)還遠(yuǎn)不止于此。高階函數(shù)只不過是個開始。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號