Saturday, January 7, 2017

函數式編程 (functional programming) -- 語言的雜交

不知道大家有沒有跟我一樣類似的經驗?想要了解 functional programming 的時候,會看到一些句子:「Lisp 語言受到 lambda calculus 的影響」「lambda calculus 是 Turing complete 。」「 Closures are poor man's objects and vice versa. 」然後,心裡冒出了許多困惑。

比方說:
(*) 那 lambda calculus 跟 functional programming 有什麼關系?為什麼需要去討論 turing complete ?
(*) 那既然 functional programming 也可以產生跟 object oriented programming 一樣的 object ,那為什麼要取名為 closures。

關於這一類的疑惑,在我學習 Lisp 語言的時候,終於漸漸地一一澄清了。而這一類困惑的源頭,我認為,來自於「語言的雜交」。近來的語言發展趨勢,許多主流語言都轉向了加入函數式編程的特色。然而,由於考慮向後相容性,加入函數式編程的特色的同時,並沒有移去本來屬於 OOP 的特色,於是就導致了用既有編程思想去理解函數式編程的困惑。

首先,C++ 語言、 Java 語言,這一類語言,本來是基於 Von Neumann 架構,由組合語言加以抽象化,而發展出來的語言設計,骨子裡,它的運算就是 state transfer 。如果把一些高級的特性一一拿去,讓它們退化,退化的過程也許是 Java 先退化成 C++ ,最後退化成 brainfuck 。這時候,我們來仔細檢驗一下 brainfuck ,真的就只能做 state transfer ,已經沒有任何函數呼叫的語法,而它依然是一種 Turing complete 的程式語言,換言之, Java 做得到的事,它也辦得到。

然而,如果某一種語言,比方說 Java 8 已經同時擁有完整的函數式語言的特性,它其實可以有另一種退化的方向, Java 先一一移除物件導向的特性,最後連 loop, goto, pointer 等特性也一一移除。最後也可以變成某種程式語言,只有 lambda calculus 的語言特性,於是,依照教科書上說的,它依然是一種 Turing complete 的程式語言。但是,沒有 goto, 沒有 loop ,這樣子的語言要怎麼做出計算呢?當函數式編程語言用極簡的形式來呈現時,我們更容易看出來,它計算的本質不是 state transfer ,而是 stateless transformations of immutable things, where things can be both data and functions, or even partially applied functions.

在理解了 lambda calculus 和 Turing machine 的計算本質性的差異之後,才能理解,同樣是函數,在 Von Neumann programming paradigm 之下的函數,它主要是用來做為程式碼模組化的功能,減少行數。真正的計算,發生在變數之間的狀態轉換。而在 functional programming paradigm 之下的函數,它必須是「無狀態的變換」 (stateless transformation) ,它本身就是真正的計算發生之所在。

更進一步,既然在 functional programming paradigm 之下,計算應該要用「無狀態的變換」來達成。程式語言自然要設計數種語法,其可以用來生成「無狀態的變換」。比方說: closure等語法適合生成容易客製化的 first order functions ,這些 first order function 又可以做為「變換」來使用 。既然要做「無狀態的變換」,程式語言自然要設計,可以達成高效能又可以無狀態的資料結構,例如: immutable data structure 。