不変データ構造 ふへんでーたこうぞう
簡単に言うとこんな感じ!
一度作ったら二度と書き換えられないデータのことだよ!「変更したい」ときは元のデータはそのままにして、変更内容を反映した新しいデータをコピーして作る仕組みなんだ。消せない付箋みたいなイメージで、改ざんされる心配がなくてバグも追いやすいってこと!
不変データ構造とは
不変データ構造(Immutable Data Structure/イミュータブル・データ・ストラクチャ)とは、一度生成したら中身を変更できないデータのまとまりのことです。プログラムの中でデータを「書き換える」のではなく、「変更を加えた新しいコピーを作る」という考え方に基づいています。
対義語は可変データ構造(Mutable)で、普段のプログラミングでは配列や辞書型オブジェクトの中身を好きなタイミングで書き換えるのが一般的です。しかし不変データ構造では、たとえばリストに要素を追加したいときも、「元のリストはそのまま残して、要素が追加された新しいリストを返す」という操作になります。
この仕組みは関数型プログラミングの根幹をなす考え方で、データが勝手に変わることで起きるバグ(副作用)を根本から防げるため、近年のフロントエンド開発やバックエンド設計でも広く採用されています。
不変データ構造の仕組み
可変 vs 不変:何が違う?
| 比較項目 | 可変(Mutable) | 不変(Immutable) |
|---|---|---|
| データの変更 | 元のデータを直接書き換える | 新しいコピーを作って返す |
| 元データ | 消える・変わる | 常に残る |
| バグの発生 | 意図しない変更が起きやすい | 変更が追跡しやすい |
| メモリ効率 | 一つのデータを再利用 | 単純コピーだと多くなる※ |
| 並列処理 | 競合・ロックが必要 | 安全(競合しない) |
※ 後述の「構造的共有」で効率化される
イメージで理解する
【可変データ構造】
リスト = [1, 2, 3]
リスト.add(4)
→ リスト = [1, 2, 3, 4] ← 元のリストが破壊される!
【不変データ構造】
リスト1 = [1, 2, 3]
リスト2 = リスト1.add(4)
→ リスト1 = [1, 2, 3] ← 元データはそのまま残る
→ リスト2 = [1, 2, 3, 4] ← 新しいデータができる
語呂合わせで覚える
「イミュータブル=イジれないブル(強い牛)」 頑丈で誰にも変えられない、強い牛のようなデータ!
構造的共有(Structural Sharing)でメモリを節約
単純にコピーを作るだけだとメモリが何倍にも増えてしまいます。そこで多くの不変データ構造ライブラリは構造的共有という技術を使っています。変更のなかった部分は元のデータへの参照を使い回すことで、コピーのコストを最小限に抑えます。
元データ: [A] → [B] → [C] → [D]
"C"を変更した新データ:
[A] → [B] → [C'] → [D]
↑新しく作る
[A][B][D] は元データと共有(コピーしない)
歴史と背景
- 1950〜60年代 — LISP言語が登場。リストを不変として扱う関数型プログラミングの原型が生まれる
- 1970〜80年代 — MLやHaskellなど関数型言語が学術分野で発展。不変性が設計の中心に
- 1990年代 — JavaやC++など手続き型言語が主流となり、可変データ構造が業界標準に
- 2000年代 — 並列・並行処理の重要性が増し、可変データによる競合問題が深刻化。不変性が再評価される
- 2013年 — Immutable.js(Facebook製)が登場。JavaScriptに不変データ構造を持ち込み注目を集める
- 2015年頃 — Redux(Reactの状態管理ライブラリ)が不変性を設計の中心に据えて普及。フロントエンド開発での採用が急増
- 2020年代 — Rust、Kotlin、Swiftなどモダン言語でも不変性を言語レベルでサポートする設計が標準化
関連する技術・ライブラリとの比較
主要な不変データ構造ライブラリ
| ライブラリ | 言語 | 特徴 |
|---|---|---|
| Immutable.js | JavaScript | Facebookが開発。List/Map/Setなどを提供 |
| Immer | JavaScript | 普通の書き方でコードを書けて内部で不変にしてくれる |
| Clojure | JVM言語 | 言語全体が不変データ構造ベース |
| Haskell | 関数型言語 | すべてのデータがデフォルトで不変 |
不変性と状態管理の関係(SVG図解)
実務でよく見る使われ方:Reduxとの組み合わせ
// ❌ 可変(NG):元のstateを直接書き換えてしまう
function badReducer(state, action) {
state.count = state.count + 1; // 元データを破壊!
return state;
}
// ✅ 不変(OK):スプレッド構文で新しいオブジェクトを返す
function goodReducer(state, action) {
return { ...state, count: state.count + 1 }; // 新しいオブジェクトを作成
}
関連する規格・RFC
※ 不変データ構造はプログラミングの設計概念であり、IETFやISOの公式規格は存在しないため、このセクションは省略します。