不可變性 (Immutability)

★★★★★Studied

FromFunctional Design

📚 From the Books

不可變性 (Immutability)ConceptParadigms

一句話的函數式定義:不使用賦值述句的程式設計。值一旦初始化就不再改變——狀態的演進靠「從舊狀態算出新狀態」,而不是覆寫舊的。沒有賦值,就沒有時序耦合、沒有競態條件。這是許多設計模式在函數式世界裡「縮成一行」的底層原因。

🧠 When to Use

TIP

訊號:你的 bug 最後是靠「交換兩行的先後順序」解決的、或是加一行 log 卻發現有三個位置可放、其中一個印出錯的值——這些都是賦值帶來的時序耦合在作祟。

⚖️ Structure & Variants

賦值 vs 初始化 —— 差別是『值會不會變』
  • 初始化val x = 0——x 此刻誕生並被設為 0;我們無從得知它是變數還是常數。
  • 賦值x = 1——x 早已存在,現在被成 1;毫無疑問它在變動。
  • 函數式的另一種說法就是**「沒有變數的編程」**:所有被命名的值都是常數,一旦初始化,其歷史完整、不變、永久存在——這就是不可變性。
用遞迴取代賦值 —— 狀態靠參數傳遞
// 命令式:靠賦值累加(sum、i 一直在變)
fun sumSquares(): Int {
    var sum = 0
    for (i in 1..10) sum += i * i
    return sum
}

// 函數式:沒有賦值,新值靠參數傳給下一次呼叫
tailrec fun sumSquares(sum: Int = 0, i: Int = 1): Int =
    if (i > 10) sum else sumSquares(sum + i * i, i + 1)
  • 想擺脫賦值,就得用遞迴:遞迴用「函式參數初始化」取代了「區域變數的賦值」。
  • tailrec(尾呼叫優化 TCO)讓遞迴不吃堆疊——所有函數式語言都以某種方式提供它。
引用透明性 —— 呼叫可用回傳值替換
  • 數學函數 y = f(x):給定 x 永遠得到同一個 y,系統當下狀態與先後順序都與它無關。
  • 這性質叫引用透明性:任何函數呼叫都能安全地用它的回傳值替換。這正是 functional 一詞的由來。
  • 純函數沒有隱藏依賴,因此好測試、好快取(memoize)、好並行。
不改狀態,怎麼寫『有狀態』的程式?
// 從舊狀態算出新狀態,而非修改舊狀態
tailrec fun run(s: State): State =
    if (s.isFinal) s else run(s.advance())
  • 每次迭代產生全新的狀態,舊狀態從未被覆寫(精神上它們都還留在某個堆疊框裡)。
  • 嚴格說,任何接受輸入的程式都不可能是純函數式(輸入不是引用透明的);但整體仍可用函數式風格寫成——這才是重點。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading