純粋関数について説明する。
純粋関数の特徴は以下である。
- 引数が同じ場合、常に同じ返り値となる。(参照透過性)
- 副作用が発生しない
例えば、下記のようなadd
関数は純粋関数である。
function add(a, b) {
return a + b
}
a=1
かつb=2
の場合、必ず3
が返る保証があるためである。(参照透過性)
参照透過性を持たない例として、ランダムな値や現在時刻を返す関数は、実行するたびに結果が変わるので純粋関数ではなくなる。(ただし、ランダム値を返す関数を返す高階関数は純粋関数にできる)
では下記の関数はどうか。
function increment(obj) {
obj.num = obj.num + 1
return obj
}
obj={ num: 1 }
の場合、必ず{ num: 2 }
が返る保証がある。
しかし、この関数は副作用を持つ。
JavaScriptの仕様として、プリミティブな値以外は値ではなく参照が渡される。その為、引数のobj
のメンバへの変更は呼び出し元が渡したオブジェクトのメンバも書き換えてしまう。
function increment(obj) {
obj.num = obj.num + 1
return obj
}
var origin = { num: 1 }
var result = increment(origin)
console.log(origin) // { num: 2 } 呼び出し元のオブジェクトのメンバに変更が入ってしまっている
console.log(result) // { num: 2 }
この関数は、関数の外への影響 = 副作用を持っている。
よって純粋関数ではない。
これ以外の副作用の例として、外部デバイス・ネットワーク・ファイルシステムとのIOや、グローバル変数・スコープ外変数などがある。
テスト性やコードのトレーサビリティの利点もある。
関数が関数外への影響(副作用)を持つということは、関数外の値やデータベースの状態を考慮してテストし、なおかつコードをトレースしなければいけなくなることが多い。
関数の中に影響を留める(=ローカリティを高める)ことは、純粋関数の使用や結合度の低さと関連している。
また、多くの有用なライブラリは、ユーザーが純粋関数のみを実装することで目的を達成できるように調整されていることが多い。
IOによる副作用を除去する方法として、依存性注入のテクニックを利用することもできる。
純粋関数ではIOが発生しないことからパフォーマンスが良くなる傾向があるが、たまに計算量の多い処理がある。
その場合、「引数が同じ場合、常に同じ返り値となる」特徴を利用して、メモ化をすることがでパフォーマンスが改善されることがある。
メモ化を行うと、引数と結果のセットがキャッシュできるため、2回目以降の呼び出しはキャッシュの参照のみを行えばよくなるからだ。
こうしたことから、純粋関数は非常に有用な概念である。