解密函數式編程 (functional programming made easy)

有個關於鋼琴家的故事是這樣子: 某樂譜裡最華麗的樂章,需要人類極限的超快指法才能彈奏。有位鋼琴家在他年老時,發現自己的體力衰退了,手指無法彈出超高速,不由得為了無法演奏這段華麗的樂章而困擾不已。所幸,不久之後,這位鋼琴家找到了很聰明的解決方案: 「透過人耳的相對節奏感來解決問題。」由於人類的耳朵對於音樂的節奏是相對的感受,所以這位鋼琴家在演奏到接近最華麗的樂章前,就先把音樂節奏的速度放慢 1/8 ,等到演奏到最華麗的樂章時,再發揮自己的最高速來彈奏。對於聽眾來說,由於之前聽到音樂的是略慢的,於是會感到強烈的反差,覺得華麗的樂章極快。甚至聽眾還會覺得,音樂家到老年時, 彈奏的技巧更高超了,最華麗的那一段樂章還比他年輕時彈得更快。

我今年 37 歲,在軟體業來講,已經算是大齡的軟體工程師。體力、腦力也不如現在二十多歲的年輕人。函數式編程,對於我來講,就像是上頭故事中鋼琴家的犯規技巧,讓我可以比十年前產出更多、更正確。那什麼是函數式編程呢? 光是這個定義,就有很多不一致的看法,不一致的看法部分,已經超出本文的篇幅。在本文裡,我想探討一個問題: 「函數式編程,是如何提高工程師的產出的?」

函數式編程:讓人需要同時處理的問題,變得更少

人腦思考的能力有其上限、受到 7 物件法則的限制、無法同時考慮太多事。程式設計的工作,如果可以切割到足夠小的範圍來一次處理,就可以讓人腦可以輕易地想清楚。反過來講,如果沒有把每次需要同時考慮的問題,透過有效的方式切分到夠小,那就會超過人腦可以清楚思考的極限。結果往往導致人會很難理解,或是說就算理解了,也很容易寫錯。文章開頭的故事裡,鋼琴家因為察覺了人類在節奏感的限制,因而利用了這個限制發展出有效的高速彈奏技巧。同樣的道理,既然我們也察覺了人類在思考能力上的限制,所以只要能夠因應這個限制來改進編程的方式,自然也可以大幅提高產出。

以下是 qsort 的兩個 Python 的實作方式,前者是傳統的命令式編程、 後者是函數式編程的作法。程式碼取自 https://rosettacode.org/

命令式編程:

函數式編程:

畫紅圈的部分,是功能等價的程式碼,它們的功能都是『分割陣列』:『取一個值 p,將比這個值小的全部放到 p 之前,將比這個值大的全部放到 p 之後。』。由於『分割陣列』這是一組完整的概念操作,無論我們是否將其獨立成一個專用的函數,它都是在編程的時候,要一次同時考慮、理解的操作。

第二個例子的實做方式,因為利用新配置的兩塊記憶體 less 和 more,來放置分割完成的結果,在實作上,就遠比第一個例子來得簡單。第一個例子為何複雜?因為它的實作同時處理了兩個問題:

  1. 分割陣列 
  2. 節省記憶體 (分割陣列的動作只使用一塊記憶體 )。

Clojure 語言的作者 Rich Hickey 特別為了第一種編程方式取了一個名字:位址導向編程 (place-oriented programming) ,相對而言,第二種編程方式,則可以稱之為:值導向編程 (value-oriented programming) 。對人腦而言,要透過『值』語義來編程,顯然是比要透過『位址』語義,來得簡單輕鬆得多。

函數式編程裡有許多技巧,可以透過讓人需要同時考慮的事變少,來有效提高軟體工程師的產出,讓我們寫得更快、更正確。而這些技巧也會付出消耗更多的記憶體做為代價,而幸運的是,我們生活在一個記憶體很便宜的年代。換言之,函數式編程,也是軟體工程師利用與享受摩爾定律成果最有效的方式之一。