跳轉到

3.1 1985 Miranda 與純度、副作用問題#

前言:惰性社群的十年醞釀#

1977 年 Backus 演說後,英國學術界出現一整批實驗性函數式語言:Turner 的 SASL(1976)與 KRC(1981)率先採用惰性求值,Henderson 的 LML、Johnsson 與 Augustsson 的 LML/Haskell 前身各據一方。David Turner 在 Kent 大學把這些想法冶於一爐,1985 年推出 Miranda——首個被商業支援、用於正式教學的純函數式惰性語言。

Miranda 的歷史地位是「收斂點」:它把此前十年分散的想法——純度、惰性、代數資料型別、模式匹配、高階函數——整合成一個內聚、可教的整體。三年內英國約 200 所大學採用。但 Miranda 是 Research Software Ltd 的商業授權產品,不能自由實作——這個限制直接引爆了 1987 年的 Haskell 委員會(3.2 節)。

核心問題:副作用為什麼是萬惡之源#

命令式程式的困難來自副作用(side effect):函數除了回傳值還修改外部狀態。純函數的定義:

\[ f \text{ 純} \iff f\; a_1\, a_2 = f\; a_1\, a_2 \ \text{(引用透明,referential transparency)} \]

引用透明使等式推理成立,而這正是 Backus 演說承諾的代數性。具體的實務紅利:

  1. 任何子表達式可被其值替換(反之亦然)→ 編譯器可自由重排、共享、消去計算;
  2. 測試即代數:純函數的測試不需要 mock 狀態;
  3. 平行化免費:無共享可變狀態即無資料競爭。

副作用還有兩個更隱蔽的破壞:求值順序變成語義的一部分(先印後算與先算後印結果不同),以及除錯時狀態的時間依賴。```text 命令式(有副作用): let a = f(3) + f(3) -- log = [3, 3] let b = f(3) * 2 -- log = [3, 3, 3] a == b(值相同)但對 log 而言兩者「做了不同的事」 ⟹ f(3) + f(3) ≠ 2·f(3)(代數分配律失效!)

純函數式(引用透明): f(3) + f(3) = 2·f(3) -- 共享子表達式可自由消除/重排 ⟹ 編譯器可做公共子表達式消除、融合(fusion)、平行化

Miranda 的激進答案是:乾脆禁止——沒有賦值、沒有可變變數、IO 只以最受限的方式提供。**純度不是性能犧牲,而是把「時間」從語義中移除。**

## Miranda 的語言形貌

```haskell
-- Miranda 語法(Haskell 的直系祖先)
sieve (p : xs) = p : sieve [x | x <- xs; x mod p > 0]
primes = sieve [2..]

take 5 primes      -- [2, 3, 5, 7, 11] —— 無限質數表!

三個特徵值得注意:

  1. 無限列表 [2..]:只在惰性(3.2 節)下合法;
  2. 列表推導 [x | x <- xs; ...]:Turner 的發明,後成為 Haskell 與 Python comprehensions 的原型;
  3. 衛式(guard)定義:f x | x > 0 = ...,函數定義像數學方程。

但純度立刻撞上現實:怎麼讀檔案?怎麼印字?Miranda 時代的答案很粗糙——用內建的受限原語或「偽裝成資料的互動」(Turner 的 dialogue 型別,List-of-requests/responses)。這個難題懸置了五年,直到 Haskell 的 Monad(3.3 節)才優雅解決。「純度與 IO 的和解」是 1990 年代函數式語言最重要的理論突破,而它的必要性正是 Miranda 逼出來的。

副作用的代數觀點:什麼被破壞了#

考慮一段共享子表達式:

// JavaScript(副作用)
let log = [];
function f(x) { log.push(x); return x * 2; }
let a = f(3) + f(3);   // a = 12,log = [3,3]
let b = f(3) * 2;      // b = 12,log = [3,3,3] —— b 與 a 「值相同」但狀態不同!

\(f(3) + f(3) = 2 \cdot f(3)\) 在此不成立(對 log 而言)。乘法分配律等代數定律的失效,使得編譯器不能做公共子表達式消除之外的大多數改寫。這個小小的觀察就是「函數式程式易於最佳化」的全部根據——Haskell 的 GHC 至今仍依賴引用透明做激進的 fusion、inlining 與 strictness analysis。

歷史意義與後續發展#

  1. Miranda → Haskell(1987-1990): Miranda 的商業授權使學界無法自由擴充與發論文,1987 年 FPCA 會議決議成立委員會設計開放標準語言,Miranda 的幾乎全部設計(語法、ADT、惰性、列表推導)被繼承。
  2. 純度的制度化:「預設純、副作用用型別標記」成為 Haskell 的核心哲學,也影響了 Scala(ZIO/Cats Effect)、Rust(&mut 獨占借用即一種受控可變性)、Python 社群的純函數風格倡議。
  3. 教學革命:Miranda 證明「第一次學程式設計可以不學賦值」,影響了之後三十年的課程設計。

Turner 本人的總結可作為本節結語:惰性求值「把程式從時間的暴政中解放出來」——不過先要付的學費,是如何在沒有狀態的世界裡做 IO。這筆帳,Haskell 用一個來自範疇論的概念來還。

重點整理#

  • Miranda(1985, Turner)是首個商業支援的純函數式惰性語言,整合了 1976-85 年的實驗成果。
  • 副作用破壞引用透明 → 代數定律失效 → 編譯器與程式設計師都失去等式推理。
  • 純度的紅利:可替換性、免費平行化、無時間依賴的除錯。
  • 列表推導、衛式、無限結構在 Miranda 中定形。
  • 純度與 IO 的衝突懸而未決(dialogue 型別過於笨拙),成為 Monad 的動機。
  • Miranda 的封閉授權直接導致 1987 年 Haskell 標準化委員會的成立。