函數式程式設計(FP)是什麼?從程式設計典範比較命令式、宣告式與物件導向,看懂 FP 為何只需要純函數
上一篇介紹了課程架構,並提到整門課最重要的概念是純函數。這一篇先退一步,回答一個更根本的問題:函數式程式設計到底是什麼?
課程教材裡有些地方附了投影片連結,可以打開講師邊講邊翻的簡報。課程的節奏是每講完一段投影片,就回到練習題動手寫。
函數式程式設計是什麼?
這個問題有很多種回答方式,講師從三個角度切入:它是一股趨勢、一種程式風格,也是一種程式設計典範。
一股趨勢,但不是曇花一現
函數式程式設計確實是一股趨勢,在 Web 開發領域尤其流行。過去大概一、二十年間,它在實務的日常軟體開發中越來越常見,Web 內外都是如此。
但它不是一時的流行。函數式程式設計早在數位電腦出現之前就存在了,最早是 1930 年代 Alonzo Church 發展 lambda calculus 時提出的數學概念。它在理論電腦科學裡已經重要了將近一百年,不會突然消失。
術語很多,但重點是概念
函數式的世界確實有很多術語,大致可以分成兩種:
- 聽起來好像應該懂的:例如 higher order(高階)、referential transparency(參照透明)
- 可能從沒聽過的:例如 monoid、functor,除非在很進階的數學課裡碰過
還有幾乎每個函數式語言 logo 裡都會出現的 lambda 符號,它到底代表什麼?講師說,這些術語之後可以自己去查,但這堂課的重點放在概念,不在術語。
原因是,我們想帶走的不是「我會一堆可以讓自己聽起來很聰明的詞」,而是「我懂一些概念,能寫出不會出錯的程式」。這跟上一篇講師說的一致:學函數式不是為了顯得比較聰明,而是為了它的具體好處。
一種程式風格
函數式程式設計也可以定義成一種程式風格,有些人稱它為函數式風格(functional style)。這裡的「風格」不是指語法、命名慣例這類表面的東西,而是一種思考程式的方式,這也是整門課要深入探討的內容。
有些語言支援這種風格,有些則不支援。一般被視為函數式語言的有:
- Haskell
- Clojure
- Elm 語言家族
- OCaml
也有專門為 Web 開發設計的,例如 Elm,以及以 Erlang 為基礎的 Elixir。
不過講師認為,我們不需要這一大堆語言,因為瀏覽器裡就有一個很棒的函數式語言:JavaScript。JavaScript 當初並不是設計成純函數式語言,這些年來或許也多了一些不那麼函數式的特性。但我們依然可以在 JavaScript 裡寫出完全合格的函數式程式,因為函數式程式設計需要的只有函數。
如果上完這門課還想繼續深入,可以再去探索其他語言與工具,但這堂課只專注在函數式 JavaScript。
程式設計典範(programming paradigm)
前面提到函數式不只是語法或 linting 層面的程式風格,而是一種思考程式的方式,這也就是所謂的程式設計典範(programming paradigm)。
典範(paradigm)泛指一種世界觀,或對「整個世界由什麼組成」的後設(meta-level)理解。放到程式設計來說,典範回答的是:
- 程式的世界由什麼組成?
- 程式裡有哪些東西?
- 這些東西彼此怎麼互動?
思考電腦程式的方式有很多種,這些不同的思考方式就叫做不同的程式設計典範。函數式程式設計是其中一種,它常被拿來跟物件導向程式設計(object-oriented programming)比較。對很多人來說,物件導向比較熟悉,尤其是從 Python、Java 這類語言轉過來的人。而在 JavaScript 裡,物件導向和函數式兩種都能寫,至少可以這麼說。
命令式 vs. 宣告式
要比較這些典範,可以先從兩個更高層級的典範看起:
- 命令式程式設計(imperative programming):物件導向是命令式的一種特例。程式由一連串命令組成,例如「做這個、做那個」「把這個值指派給這個變數」「用這個變數呼叫這個函數」。開發者一步一步告訴電腦怎麼執行,電腦照著做
- 宣告式程式設計(declarative programming):函數式是宣告式的一種特例。開發者只告訴電腦想要的輸出或結果,至於怎麼得到這個結果,交給電腦自己處理
我們平常寫 JavaScript,大多習慣命令式的寫法。但宣告式語言其實大家都碰過:
- HTML:HTML 不會說「請照這個方式套用所有樣式表」或「請照這個方式載入所有連結」,它只說「瀏覽器,這是我想看到的頁面,請給我」
- SQL:SQL 是資料庫查詢語言,也是宣告式語言的經典例子。我們不會告訴電腦怎麼執行 lookup、select 或 join,只說想拿到什麼資料,細節交給底層實作處理
上一篇提到 React 用宣告式的方式描述「我希望這個元素長這樣」,跟 HTML 的例子是同一種思維:描述想要的結果,而不是達成結果的步驟。
所以寫函數式程式時,比較不會把程式想成「先做這個、再做那個」的步驟清單,而是把整個程式想成一次把輸入轉換成輸出的過程。這種轉換怎麼運作、寫起來是什麼感覺、需要哪些工具,是後續課程的重點。
物件導向 vs. 函數式
兩者組織程式的基本單位不同:
- 物件導向:重點是把狀態包進一個個小物件裡。這裡的狀態指的是在程式執行過程中會隨時間改變的資訊或值。物件之間透過互相呼叫方法(method)來傳送與接收資訊
- 函數式:所有東西都包裝成函數這個單位
函數式程式設計真正需要的只有一種建構元素:純函數(pure function)。整門課要深入探討的,就是純函數的各種細節。
延伸閱讀:Mary Rose Cook 的文章
講師特別推薦一篇啟發了這整門課的文章:Mary Rose Cook 寫的〈An introduction to functional programming〉。文章有點年代了,由 Recurse Center 發表。Recurse Center 是一個很棒的程式設計社群,在紐約也有線上活動,講師至今仍是其中一員。
講師大約在 2015 年參加了 Recurse Center 的 programming retreat,跟 Mary Rose Cook 一起學習。講師認為 Mary Rose Cook 很擅長教函數式程式設計的基本原則,又不會被術語綁住。這篇文章雖然是用 Python 寫的,但原則基本上相同,講師就是用這些原則在 JavaScript 的世界裡學會函數式程式設計,也就是這門課接下來要介紹的內容。
文章連結可以在投影片中找到,投影片最後也附了其他延伸閱讀的連結。
複習
函數式程式設計被視為哪一種程式設計的子集?
宣告式程式設計
在函數式程式設計中,最主要的組織單位是什麼?
純函數
命令式與宣告式程式設計在下指令的方式上有什麼不同?
命令式程式設計是開發者一步一步下命令給電腦;宣告式程式設計則是開發者描述想要的輸出,由電腦決定怎麼達成
舉出兩個宣告式程式語言的例子
HTML 與 SQL
相較於物件導向,函數式程式設計怎麼處理狀態與行為?
函數式程式設計把所有東西都包裝在函數裡;物件導向則是把狀態包裝在物件裡,物件之間透過互相呼叫方法來互動
小測驗
函數式程式設計屬於哪一種程式設計典範?
宣告式程式設計函數式程式設計的基本單位是什麼?
純函數宣告式程式設計和命令式程式設計有什麼不同?
宣告式程式設計著重在描述想要的結果,而不是具體的步驟下列哪一項是宣告式程式設計的特徵?
指定想要的輸出,讓系統自己決定怎麼達成此文章是 FrontendMasters 上的 Functional JavaScript First Steps, v2 課程筆記
