参照透過性 さんしょうとうかせい
簡単に言うとこんな感じ!
「同じ入力を渡したら、いつでも必ず同じ答えが返ってくる」という性質だよ!たとえば add(2, 3) は何回呼んでも必ず 5 になるよね。これが参照透過性があるってこと!逆に「今日の日付を返す関数」は呼ぶたびに結果が変わるから参照透過性がない、ってことになるんだ。
参照透過性とは
参照透過性(Referential Transparency)とは、「ある式をその評価結果の値に置き換えても、プログラム全体の振る舞いが変わらない」という性質のことです。もっと平たく言えば、同じ引数を渡せば何度呼んでも必ず同じ値が返ってくるということです。数学の関数 f(x) = x + 1 は x が 3 なら常に 4 を返しますが、これが参照透過性の本質です。
参照透過性を持つ関数は純粋関数(Pure Function)とも呼ばれます。純粋関数は「外の世界(グローバル変数・データベース・現在時刻・乱数など)に依存しない」「外の世界を変更しない(副作用がない)」という2つの条件を満たします。この性質があると、コードの動作を予測しやすくなり、テストやデバッグが格段に楽になります。
関数型プログラミング(Haskell・Elm・Scalaなど)では参照透過性が中心的な概念として扱われていますが、JavaScriptやPythonのような言語でも「参照透過性を意識した書き方」をすることで、バグが少なく保守しやすいコードを書けます。ビジネスシステム開発における品質向上の観点からも、近年注目が高まっている考え方です。
参照透過性がある/ない関数の違い
| 観点 | 参照透過性あり(純粋関数) | 参照透過性なし(非純粋関数) |
|---|---|---|
| 同じ入力への結果 | 常に同じ | 変わる可能性がある |
| 外部状態への依存 | なし | あり(DB・時刻・グローバル変数など) |
| 外部状態の変更(副作用) | なし | あり(ファイル書き込み・画面出力など) |
| テストのしやすさ | 非常に高い | モックや準備が必要 |
| 実行順序の依存 | なし | あり |
| 典型例 | Math.abs(-3) → 常に 3 | Date.now() → 呼ぶたびに変わる |
【純粋関数の例】 【非純粋関数の例】
function add(a, b) { let count = 0;
return a + b; function increment() {
} count++; ← 外部を変更!
// add(2, 3) は }
// 何度呼んでも 5 // 呼ぶたびに count が変わる
覚え方:「数学の関数と同じかどうか?」
数学では f(x) = x² は x = 3 なら絶対に 9 です。昨日も今日も明日も変わりません。「数学の関数と同じルールで動くか?」と問いかけてみると、参照透過性があるかどうかをすぐ判断できます。「引数以外のものを見ていないか?」「引数以外のものを変えていないか?」——この2点がチェックポイントです。
参照透過性を壊す代表的なパターン
- グローバル変数の読み書き:関数の外にある変数を参照・変更する
- 現在時刻・乱数:呼ぶたびに結果が変わるものに依存する
- I/O操作:ファイル読み込み・DB検索・コンソール出力など
- 例外のスロー:同じ入力でも例外が出る場合がある
- ミュータブルなオブジェクトの変更:引数で受け取ったオブジェクトを直接書き換える
歴史と背景
- 1930年代:数学者アロンゾ・チャーチがラムダ計算(λ計算)を考案。「関数を数学的に定義する」という考え方の原点になる
- 1950年代:LISPが登場。関数を第一級市民として扱うプログラミング言語が生まれる
- 1970〜80年代:Haskellの前身となるMLなどの関数型言語が研究され、「副作用のない純粋な関数」という概念が整理される
- 1987年:Haskellの設計が始まる。参照透過性を強制的に守らせる言語設計として完成(1990年に正式リリース)
- 2000年代以降:並列処理・マルチコア時代の到来により、「副作用がなければ安全に並列実行できる」という参照透過性の実用的メリットが再評価される
- 2010年代:ReactのようなUIフレームワークが「状態管理を純粋関数で行う」設計(Reduxなど)を採用し、フロントエンド開発でも普及
- 現在:RustやKotlin、TypeScriptなどのモダンな言語・エコシステムでも「できるだけ副作用を局所化する」設計が主流になりつつある
純粋関数と副作用のある処理をどう共存させるか
現実のシステムはDBへの書き込みや画面への出力など、必ず「副作用」が必要です。では参照透過性はあきらめるしかないのでしょうか?実際の開発では「コアのロジックを純粋関数で書き、副作用は端に追い出す」という設計パターンが使われています。
この設計を「関数型コア、命令型シェル(Functional Core, Imperative Shell)」パターンと呼びます。コアのロジックを純粋関数で書くことで、そこだけを集中してユニットテストでき、品質を高めやすくなります。副作用のある部分(DBアクセスなど)は端に局所化し、統合テストで確認します。
参照透過性が実務で役立つ場面
| 場面 | 参照透過性があると何が嬉しいか |
|---|---|
| ユニットテスト | モックや事前準備なしに、入力と出力だけでテスト可能 |
| デバッグ | 「この関数を呼んだとき何が起きたか」が完全に再現できる |
| 並列処理 | 副作用がないので複数スレッドで安全に同時実行できる |
| メモ化(キャッシュ) | 同じ引数なら必ず同じ結果なので、計算結果を安全にキャッシュできる |
| リファクタリング | 式を値に置き換えても動作が変わらないので、安心してコードを整理できる |
関連用語
- ./053-pure-function.md — 参照透過性を満たす関数。同じ入力に同じ出力、副作用なし
- ./055-side-effect.md — 関数の外部に影響を与える処理。参照透過性を壊す原因
- ./056-functional-programming.md — 関数を中心に組み立てるプログラミングのスタイル
- ./057-immutable.md — 一度作ったら変更できないデータ。参照透過性と相性が良い
- ./058-memoization.md — 純粋関数の結果をキャッシュして高速化するテクニック
- ./059-lambda-calculus.md — 参照透過性の数学的な土台となったチャーチのラムダ計算
- ./060-unit-testing.md — 純粋関数はそのままユニットテストが書きやすい
- ./061-higher-order-function.md — 関数を引数や戻り値にできる関数。関数型プログラミングの基本道具