可變狀態的問題

const user = { name: "Alice", scores: [90, 85, 92] };
 
function addBonus(u) {
    u.scores.push(100);  // 直接修改傳進來的物件
    return u;
}
 
addBonus(user);
console.log(user.scores);  // [90, 85, 92, 100]  ← 外部也變了!

你把 user 傳給一個函式,你不知道那個函式有沒有修改它。如果程式複雜了,有十個函式都可能修改 user,追蹤 user.scores 什麼時候被改是噩夢。

Immutability 的原則:不改已有的,建立新的。

function addBonus(u) {
    return { ...u, scores: [...u.scores, 100] };  // 建立新物件
}
 
const newUser = addBonus(user);
console.log(user.scores);     // [90, 85, 92]  ← 原本的不變
console.log(newUser.scores);  // [90, 85, 92, 100]

Immutability 的好處

1. 並發安全(Concurrency Safe)

如果資料不可變,多個 thread 同時讀取不需要鎖——不可能有 race condition(沒有人在寫,讀再多次都安全)。Erlang / Haskell / Rust 用 immutability 作為並發安全的基礎。

2. 容易推理(Easier Reasoning)

看到一個 immutable 物件,你知道它從建立後就不會改變。函式的輸出只取決於輸入,不取決於「某個全域狀態在某時間點是什麼值」。

3. 時間旅行(Time Travel)

Redux 的 time-travel debugging、React 的 shouldComponentUpdate——都是因為 immutable state 讓「比較前後的差異」變得容易(淺比較就夠了,prev !== next)。


各語言的 Immutability

JavaScript / TypeScript

// const 讓變數不能重新賦值,但不讓物件本身不可變
const arr = [1, 2, 3];
arr.push(4);  // OK,arr 本身還是同一個陣列
 
// Object.freeze 讓物件不可修改(淺層)
const obj = Object.freeze({ x: 1, y: { z: 2 } });
obj.x = 10;  // 靜默失敗(strict mode 下報錯)
obj.y.z = 20;  // 還是可以改,因為 freeze 只有一層
 
// Immutable.js / Immer:深層 immutable 工具

Python

# tuple 不可變,list 可變
point = (1, 2)  # immutable
 
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
    x: float
    y: float
 
p = Point(1.0, 2.0)
p.x = 3.0  # FrozenInstanceError

Rust:預設不可變,需要 mut 關鍵字才能修改——Immutability 是語言的預設值。


Immutability 的代價

每次修改都建立新物件,記憶體使用量增加。對大型資料結構(百萬個 element 的陣列),每次操作都深拷貝會很昂貴。

解法:Persistent Data Structure(結構共享)——新物件和舊物件共享未被修改的部分,只複製改動的部分。Clojure 的資料結構、Immer.js、Java 的 PersistentHashMap 都是這個實作。


和 DDD Value Object 的關係

DDD 的 Value Object(見 C04)天然就是 Immutable 的——Money(100, "USD")Money(100, "USD") 是相等的,不是同一個物件,而且 Money 的值一旦建立就不應該被修改(要「加錢」就建一個新的 Money)。Immutability 是 Value Object 設計的核心約束。