第 8 / 10 部 待錄

GUI與函式式核心

《Functional Design》Podcast 準備稿:GUI 與函式式核心

書名: Functional Design(Principles, Patterns, and Practices) 作者: Robert C. Martin(Uncle Bob) 系列: Reading Uncle Bob(恩普拉氏) 公開標題建議: Reading Uncle Bob|Functional Design.GUI 本質是改螢幕,那 FP 怎麼活 (8/10) 涵蓋範圍: 第14章 GUI(海龜繪圖)

背景速覽

上一集講了測試與併發的真相,這一集處理 FP 最尷尬的一塊地盤:GUI。因為 GUI 的本質就是「不斷修改螢幕狀態」——這跟「不可變」聽起來水火不容。Uncle Bob 用一個剛好大小的範例(Quil 上的海龜繪圖 turtle graphics)示範:函式式風格怎麼和一個必然有副作用的 GUI 框架乾淨共存。這集篇幅單章、主題集中,是整季少數可以從頭到尾跟著一個完整小程式走的一集,很適合做「具體可感」的收聽體驗。

一句話重點

GUI 的本質是改變螢幕狀態,無法純函式化——但 FP 的解法不是逃避,而是把所有狀態改變集中到「更新迴圈的尾遞迴位置」這一個受控的角落,讓純函式邏輯與必要的副作用乾淨分離;這個「函式式核心 + 狀態邊界」就是與 GUI 共存的標準解。

值得討論的重點

1. 框架選對了,FP 就能優雅;選錯了,「極度痛苦」

Uncle Bob 用過兩個 Clojure 的 GUI 框架,差別大到令人震驚:Quil(基於 Java 的 Processing)「裝得像函式式」,與 FP 合作愉快;SeeSaw(基於老牌 Java Swing)「完全不函式式」,依賴大量必須持續更新的可變狀態,在 FP 程式裡用起來「極度痛苦」。這個對比本身就是一條工程教訓:當你的核心是函式式的,框架的「函式式友善度」會決定你的痛苦程度。Quil 之所以好,是它把底層的可變性包起來、對外裝成「給我舊狀態、我回傳新狀態」的形態。

2. 把 GUI 迴圈想成一個尾遞迴

Quil 的核心是三個回呼:setup(啟動時跑一次)、update(每幀把狀態推進 1/60 秒)、draw(每秒 60 次重畫,畫面不記得任何東西、所有內容每次重畫)。Uncle Bob 的關鍵洞見是把它看成一個極簡的尾遞迴迴圈(loop [state (setup)] (draw-state state) (recur (update-state state)))。這樣一來,畫面內容就是「尾遞迴的值」,而即使我們在修改畫面,修改也只發生在尾遞迴位置——是(差不多)無害的。這呼應了第一集「不可變性 + TCO」的觀念:所有必要的可變性都被擠到那個唯一的、受控的位置。

3. channel 解耦「下指令」與「執行指令」

海龜繪圖的架構很值得講。使用者寫的腳本(forwardrightpen-down)不直接改海龜,而是把指令送進一個 channel(async/chan);update 迴圈再從 channel 把指令拉出來、翻譯成對海龜狀態的更新。這是一個漂亮的解耦:寫腳本的人完全不碰狀態,狀態的改變全集中在 update 迴圈。海龜本身用 spec 嚴格描述(position、heading、pen、lines……),make:post 條件 assert 自己回傳的海龜符合 spec——確保更新大型結構時沒弄壞某個小角落。整個系統:腳本 → channel → update(改狀態)→ draw(重畫),各司其職。

注意事項

⚠️ 這集是單章、程式碼導向,最容易講成「逐函式 walkthrough」而讓聽眾睡著。要抓住三個概念講,別逐個函式念:(1)框架函式式友善度決定痛苦程度;(2)GUI 迴圈=尾遞迴、可變性被擠到尾位置;(3)channel 解耦下指令與執行。海龜怎麼畫三角形這種細節完全可略。

⚠️ Uncle Bob 自己很誠實地說 polygon 那個畫圖函式「並非函式式——它沒有回傳值,純靠副作用畫圖」,但整個框架是函式式的,「大概是 GUI 程式所能達到的最函式式的形態」。這句平衡的話要講出來——FP 不是要消滅所有副作用,是要把副作用隔離到邊界。別讓聽眾以為 FP 連畫個圖都要純函式。

⚠️ 「函式式核心 + 命令式外殼」這個模式上一集(EP2)已經提過(spacewar 的 world),這集是它在 GUI 場景的具體落地。錄製時可以呼應前面,但別重複講解,這集聚焦「GUI 這個特例怎麼套這個模式」。

專家補充

💡 海龜繪圖(turtle graphics)本身是一段動人的計算機教育史。它由 Seymour Papert 在 1960 年代末為 Logo 語言發明——Papert 是 AI 先驅、建構主義學習理論大師,他的理念是「讓孩子透過命令一隻海龜畫圖來學習程式與幾何」。Uncle Bob 自己還用 Commodore 64 的 Logo 海龜寫過 Lunar Lander 遊戲。這個冷知識讓這集多了人文溫度,很適合當開場。

💡 「函式式核心 + 命令式外殼」(Functional Core, Imperative Shell)是現代軟體架構的重要模式,由 Gary Bernhardt 在著名的 “Boundaries” 演講中命名推廣。它的精神和 Clean Architecture 一致——把純粹的、可測試的業務邏輯放中心,把骯髒的 I/O、UI、框架推到外圈。React 的「UI 是狀態的純函式 view = f(state)」、Elm Architecture 的 model/update/view,全都是這個模式的變體。Uncle Bob 的 Quil 海龜,等於是這個模式在桌面 GUI 上的一個樣本。

💡 Quil 的 setup/update/draw 三回呼,和 Elm Architecture(model/update/view)、Redux(state/reducer/render)幾乎是同構的——這不是巧合。「不可變狀態 + 純粹的 update 函式 + 從狀態算出畫面的純粹 view」是 FP 處理 UI 的通用配方,從桌面到前端網頁都適用。懂 React/Redux 的聽眾聽到這裡會立刻有「啊這我天天在寫」的連結——原來他們早就在寫函式式 GUI 了。

討論問題

🎙️ 開場鉤子:「GUI 的本質是什麼?是不斷地修改螢幕——畫一條線、移動一個游標、刷新一幀。可是函式式編程的信條是『什麼都不要改』。這兩個怎麼共存?今天用一隻會畫圖的小海龜來回答。」

🎙️ 自問自答:「既然 GUI 一定要改螢幕,那 FP 在這裡不就破功了嗎?」——不是破功,是劃界。FP 不要求你消滅副作用,而是要求你把它關進一個籠子——所有狀態改變只發生在 update 迴圈的尾遞迴位置,其餘邏輯全是純函式。骯髒的事集中在一個角落,乾淨的事佔據其餘 99%。

🎙️ 帶走的一題:「如果你寫前端(React、Vue)或任何有畫面的程式——回想一下:你的『業務邏輯』和『改畫面/呼叫 API』是混在一起,還是分開的?試著把純粹的計算抽成不碰任何 I/O 的函式,把副作用推到邊界。這就是 Uncle Bob 海龜程式在做的事。」

更大範圍關聯

  • 第四部實務的第三章:補齊測試/併發/GUI 三章的最後一塊。下一集進入第五部「設計模式」。
  • 函式式核心 + 命令式外殼:接 Gary Bernhardt 的 “Boundaries” 演講、Clean Architecture 的同心圓——把純邏輯放中心、I/O 推外圈的通用架構智慧。
  • 與前端框架同構:Quil 的 setup/update/draw 約等於 Elm 的 model/update/view 約等於 Redux 的 state/reducer/render——FP 處理 UI 的通用配方,是給前端聽眾最有共鳴的橋。
  • 教育史源頭:海龜繪圖 → Seymour Papert → Logo → 建構主義學習理論,是計算機教育的一段經典。用 [軟體工程領域切分藍圖] 看,這集落在「函式式 / UI 架構」的交界。

錄製建議

  • 建議時長:約 18 分鐘(單章、主題集中,可以是整季較短、較輕鬆的一集)。配比:海龜歷史 + 框架對比約 5 分、GUI 迴圈即尾遞迴約 6 分、channel 解耦 + 整體架構約 7 分。
  • 討論策略:這集很適合走「具體、可感、輕鬆」的路線,當作 SOLID(EP6)那種重集之後的調劑。用 Seymour Papert 和海龜的故事溫暖開場,中段把「GUI 迴圈=尾遞迴、可變性擠到尾位置」這個核心觀念講透,結尾用「你寫 React 其實就在寫函式式 GUI」這個連結,把抽象的 FP 拉回聽眾的日常。
  • 結尾預告:「下一集進入很多人覺得『FP 不需要』的領域——設計模式。Uncle Bob 要證明 GOF 那些經典模式在函式式世界不但沒消失,還活得好好的。」