函數語言程式設計思維在三行程式碼情書中的應用

阿里云云棲社群發表於2019-01-19


函數語言程式設計概述

如今主流的程式語言,函數語言程式設計正規化或多或少都融入其中成了“標配”,或者說主流語言都在進行函式式方面的擴充,這是一個大趨勢。以Java為例,隨著 Lambda塊Stream API 等這種高階函式的加持,Java總算是裝備了函式式這一利器;博大精深的C++也在2011版的語言標準里加入了Lambda塊的支援;再比如前一段時間我初步體驗了的 Groovy語言,雖然其執行於JVM之上,然而其對 動態語言函數語言程式設計正規化 以及 超程式設計功能 的加持所帶來的表現力和簡潔性可以說甩了Java幾條街,可以利用Groovy的所有動態功能構建高效能的JVM應用、將開發效率提高几個數量級。語言的例子有很多,我不一一列舉。

注: 本文首發於 My 公眾號 CodeSheep ,可 長按掃描 下面的 小心心 來訂閱 ↓ ↓ ↓

CodeSheep · 程式羊



為什麼要使用函數語言程式設計正規化

這裡講幾個函數語言程式設計的典型特點,區別的物件那就是傳統的指令式程式設計

指令式程式設計 VS 函數語言程式設計

  • 0x01. 更高層次的抽象(高階函式)

用高階抽象來取代基本的控制結構本身就是一個全新的思考方式,這樣可以讓開發者聚焦精力於業務場景而無需費心複雜地層運作

舉個栗子:將一個字串集合中的所有單詞轉為大寫,我們用Java語言來實現

如果按照傳統的指令式程式設計的解法,那接下來不出意外我們得來寫迴圈、遍歷這種迭代操作了:

for (int i=0; i<wordList.size(); i++) {
   wordList.get(i).toUpperCase();
}
複製程式碼

但如果使用Java的函數語言程式設計正規化,一切都是那麼的優雅,一句話搞定

wordList.stream.map( w -> w.toUpperCase() )
複製程式碼

這裡的map()函式就是所謂的高階函式,我們用高階函式代替了底層的迭代,因為我們並沒有處理細節,我們僅僅定義了對映的邏輯,迭代由高階函式來自動完成!

  • 0x02. 提升程式碼訊雜比(簡潔性)

區別於面嚮物件語言用抽象來封裝不確定因素,函數語言程式設計通過儘量減少不確定因素來使程式碼極度簡潔

上面的例子對於本條優點的展現我想應該也不必多說了

  • 0x03. 控制權轉交於執行時(動態性)

區別於傳統的編譯形語言,配備函數語言程式設計正規化的動態語言更多的將控制權轉交到語言執行時手裡,獲得的則是更高的靈活性、表現力和效能權衡。

這三點優點將在接下來的例子中切實的感受並領會!



函數語言程式設計例析

舉例1:詞頻統計

做的事情很簡單:給定一個單詞集合,統計出集合中除了助詞(如ofonthe等)之外的單詞出現的頻次,不區分大小寫

命令式解法: 至少分為以下幾大步

  • 先進行迴圈迭代
  • 然後統一將單詞轉為小寫
  • 然後判斷單詞是否是助詞
  • 最後進行詞頻統計
public class WordCount {

    // 定義一個助詞集合,這些單詞不參與計數
    private Set<String> auxiliaryWordSet = new HashSet<String>() {{
       add("of"); add("the"); add("to"); add("and"); add("so"); add("are");
    }};

    // 傳統命令式解法實現的詞頻統計函式
    public Map doWordCount( List<String> context ) {
        Map<String,Integer> result = new HashMap<String, Integer>();
        for ( String word:context ) {  // 迴圈迭代
            String lowerCaseWord = word.toLowerCase();  // 將單詞統一轉換為小寫
            if( !auxiliaryWordSet.contains(lowerCaseWord) ) {
                if( null == result.get(lowerCaseWord) )
                    result.put( lowerCaseWord, 1 );
                else
                    result.put( lowerCaseWord, result.get(lowerCaseWord)+1 );
            }
        }
        return result;
    }

    // main() 函式
    public static void main(String[] args) {
        List<String> wordList = new ArrayList<String>() {{
            add("The"); add("Products"); add("of"); add("Samsung"); add("and"); add("Apple");
            add("are"); add("so"); add("amazing"); add("especially"); add("Apple");
        }};

        WordCount wordCount = new WordCount();
        Map res = wordCount.doWordCount( wordList );
        System.out.print(res); // 列印:{apple=2, amazing=1, samsung=1, especially=1, products=1}
    }
}
複製程式碼

函式式解法:

如果我們用Java的Stream API和Lambda塊所構成的函式式正規化來重寫 doWordCount() 函式,一切都將如此簡潔:

public Map doWordCount2( List<String> context ) {
    Map<String,Integer> result = new HashMap<String, Integer>();
    context.stream().map( w -> w.toLowerCase() )
            .filter( w -> !auxiliaryWordSet.contains(w) )
            .forEach( w -> result.put( w, result.getOrDefault(w,0) + 1 ) );
    return result;
}
複製程式碼

備註:這裡的getOrDefault是Java的Map提供的一個便利函式,意思是:在Map中若沒有找到給定的key時,返回一個“預設值”

對比命令式解法,使用者省去了很多繁瑣的迭代和判斷,我們只講焦點聚焦在業務邏輯之上,程式碼訊雜比提升不小吧!


舉例2:連詞成句

給定一個離散的單詞集合,我們想將字母數大於1的單詞的首字母大寫後,用 短橫線- 連線起來成為一個句子

命令式解法:

public class WordConnect {

    // 將單詞的首字母大寫
    public String capitalizeFirstLetter( String s ) {
        return s.substring(0,1).toUpperCase() + s.substring(1,s.length() );
    }

    // 連詞成句
    public String connectWord( List<String> context ) {
        StringBuilder result = new StringBuilder();
        for ( String word: context ) {
            if ( word.length() > 1 ) {
                result.append( capitalizeFirstLetter(word) );
                result.append("-");
            }
        }
        return result.substring(0,result.length()-1).toString();
    }

    // main()函式
    public static void main(String[] args) {
        List<String> wordList = new ArrayList<String>() {{
            add("The"); add("Products"); add("of"); add("Samsung"); add("and"); add("Apple");
            add("are"); add("so"); add("amazing"); add("especially"); add("Apple");
        }};

        WordConnect wordConnect = new WordConnect();
        String res = wordConnect.connectWord( wordList );
        System.out.print(res); // 列印:The-Products-Of-Samsung-And-Apple-Are-So-Amazing-Especially-Apple
    }
}
複製程式碼

函式式解法1: Java Steam API 和 Lambda塊實現

public String connectWord( List<String> context ) {
    return context.stream().filter( w -> w.length()>1 )
            .map( w -> capitalizeFirstLetter(w) )
            .collect( Collectors.joining("-") );
}
複製程式碼

我什麼都不想說了,這不要太簡潔好吧!

函式式解法2: Groovy語言實現

public String connectWord( context ) {
    context.findAll { it.length() >1 }
    .collect { it.capitalize() }
    .join '-'
}
複製程式碼

關於Groovy語言的初體驗,可以參考我的文章:Groovy初體驗:構建高效能JVM應用



函式式最佳實踐:高效編寫三行情書

還記得去年的520,為了表達心中對於老婆無限的、無法表達的愛,我想寫一封不超過三行的程式碼情書,我更想用盡可能短的程式碼來儘可能多地表達,於是我選擇了函數語言程式設計。

我的520三行程式碼情書在此:

public TimeRiver timeFlow( List<DaysMeetYou> days ) {
    return (TimeRiver)days.stream()
        .filter( n->theDaysNotWithYou(n) )
        .map( e->accompanyByMyLove(e) )
        .collect( Collectors.joining("❤️") );
}
複製程式碼

我的520三行程式碼情書



後記

文中提到的Groovy動態程式語言,作者體驗過一點,可以參考:Groovy初體驗:構建高效能JVM應用

如果有興趣,也來看看作者一些關於容器化、微服務化方面的文章:

作者更多的原創文章:在此


CodeSheep

相關文章