イミュータブル(Immutable)とは、一度作られたら中身を変更できない性質を指す。日本語では不変と訳され、値を書き換えたい場合は元のものを変えるのではなく、変更を反映した新しいものを作り出す。文字列や数値がこの性質を持つ言語は多く、近年は関数型プログラミングの普及とともに、意図的に不変なデータ構造を選ぶ設計が広く支持されるようになった。プログラムの不具合の多くは、「いつの間にか値が変わっていた」ことに起因する。ある場所で書き換えたデータが、別の場所の処理に予期せぬ影響を及ぼす。この種の問題は、規模が大きくなり並行処理が絡むほど追跡が困難になる。イミュータブルは、変更を許さないという単純な制約によって、こうした予期せぬ影響を構造的に締め出す考え方であり、コードの信頼性を根本から高める手段として重視されている。
イミュータブルの仕組み
イミュータブルは、生成時に内容を確定させ、以後の変更操作を新しい値の生成に置き換えることで成り立つ。
- 生成時の確定
作られた時点で中身が定まり、以後は書き換えられない。参照している側は、いつ見ても同じ内容であることが保証される。
- 変更は新規生成で表す
値を変えたい場合、元を書き換えず変更後の内容を持つ新しいものを作る。元の値はそのまま残り、他への影響が生じない。
- 共有の安全性
変更されないため、複数の場所から同じ実体を安全に参照できる。複製を作らずに共有でき、無駄なコピーを避けられる。
- 構造の共有による効率化
新しく作る際、変更のない部分は元の構造を再利用する実装が多い。全体を複製せずに済み、性能上の不利を抑えられる。
イミュータブルのメリット
不変であることは、コードの信頼性と扱いやすさを大きく高める。得られる利点を見ていく。
- 予期せぬ変更の防止
どこかで勝手に書き換わることがない。渡した値が呼び出し先で変えられる心配がなく、処理の影響範囲が明確になる。
- 並行処理での安全性
複数の処理が同時に読んでも競合が起きない。排他制御が不要となり、並行処理の設計が格段に単純になる。
- 動作の予測しやすさ
値が変わらないため、コードを読んだとおりに動く。追いかけるべき状態が減り、理解と保守が容易になる。
- 不具合調査の容易さ
いつどこで値が変わったかを追う必要がない。原因の候補が大きく絞られ、調査にかかる時間が短縮される。
- 履歴の保持
変更のたびに新しい値が作られるため、過去の状態が残る。取り消し機能や変更履歴の実装が自然に行える。
イミュータブルのデメリット・注意点
利点の多いイミュータブルにも、状況によっては不利になる面がある。注意点を押さえておきたい。
- メモリ消費の増加
変更のたびに新しい実体が作られるため、消費量が増えうる。大きなデータを頻繁に更新する場面では無視できない。
- 処理性能への影響
生成のたびに確保と初期化が発生する。細かな更新を大量に繰り返す処理では、書き換える方式より遅くなることがある。
- 記述が冗長になる場合
一部だけ変えたい場合も、新しい値を組み立てる記述が必要になる。言語の支援が乏しいと、コードが煩雑になりやすい。
- 浅い不変性の落とし穴
外側が不変でも、内部に持つ要素が可変なら書き換えられてしまう。全体が本当に不変かは、構造を通して確認する必要がある。
イミュータブルの活用例
イミュータブルは、信頼性が求められる多くの場面で採用されている。代表的な活用シーンを紹介する。
- 関数型プログラミング
状態を変えないことを基本とする設計の中核をなす。同じ入力に必ず同じ結果を返す性質が、この不変性から導かれる。
- 状態管理ライブラリ
画面の状態を不変なものとして扱い、更新のたびに新しい状態を作る。変更の検知が容易になり、再描画の判断も単純化する。
- 並行処理での共有データ
複数の処理から参照されるデータを不変にする。ロックを使わずに安全な共有ができ、性能と単純さを両立できる。
- 設定値の保持
起動時に読み込んだ設定を不変として扱う。実行中に書き換わることがなく、想定外の挙動を防げる。
- 値オブジェクトの表現
金額や日付など、それ自体が意味を持つ値を不変として設計する。同じ値なら同じものとして扱え、比較や受け渡しが安全になる。
イミュータブルとミュータブルの違い
対をなすミュータブルと比べることで、それぞれの性格が明確になる。両者の違いを整理する。
- 変更の可否
イミュータブルは生成後に中身を変えられない。ミュータブルは自由に書き換えられ、同じ実体の内容が変化していく。
- 共有時の安全性
イミュータブルは共有しても影響が及ばない。ミュータブルは共有すると、一方の変更が他方に波及する危険を伴う。
- 性能面の特性
ミュータブルは書き換えのみで済み効率がよい。イミュータブルは生成の負担があるが、複製や排他制御が不要という利点で相殺しうる。
- 使い分けの指針
共有される値や設定にはイミュータブル、局所的で更新が頻繁な処理にはミュータブルが向く。既定を不変とし必要な箇所だけ可変にするのが定石だ。
イミュータブルを扱う際のポイント
イミュータブルを活かすには、設計時の判断が重要になる。実務で意識したい要点を挙げる。
- 既定を不変にする
まず不変で設計し、性能上の必要が生じた箇所だけ可変にする。この順序が、不具合の少ない設計につながる。
- 内部まで不変にする
外側だけ不変にしても、内部の要素が可変では意味が薄い。保持する要素まで含めて不変性を確保したい。
- 性能は測ってから判断する
生成の負担を懸念して可変を選ぶ前に、実際に測定する。多くの場合、想定より影響は小さく、可読性の利点が上回る。
まとめ
イミュータブルは、一度作られたら中身を変更できない性質であり、値を変えたい場合は新しいものを生成することで表現する。予期せぬ変更の防止や並行処理での安全性、動作の予測しやすさといった利点により、コードの信頼性を根本から高める。一方でメモリ消費や生成の負担、浅い不変性の落とし穴といった注意点もあるため、内部まで含めた不変性の確保と、必要に応じた使い分けが求められる。既定を不変とし、性能は測ってから判断するという姿勢が実務では有効だ。変わらないという単純な制約が、複雑さを抑える強力な武器となる。手元のコードで、不変にできる値がないか見直してみるとよいだろう。
