純粋関数とは?同じ入力、同じ出力
純粋関数は同じ入力に対して常に同じ出力を返し、観測可能な副作用を一切引き起こさないため、その引数以外の何にも依存しません。
純粋性は単純でありながら強力な性質です。純粋関数はその入力によって完全に決まります。同じ引数を与えれば常に同じ結果を返し、実行してもプログラム内の他の何も変えません。時計を読むこともなく、ディスクに書き込むこともなく、共有状態をmutateすることもありません。その予測可能性が、純粋関数をテストと推論が最も容易なコードにします。
純粋性の2つのルール
関数が純粋であるのは、(1) その戻り値が入力のみに依存し、(2) 副作用を一切生じない場合です。外部状態のmutationも、I/Oも、ランダム性もありません。どちらかのルールを破れば、その関数は不純です。
純粋性が重要な理由
- テストが自明: セットアップもmockも不要で、入力と期待される出力だけ。
- cache(メモ化)が安全: 与えられた入力に対して結果が決して変わらないため。
- 並列実行が安全: 競合する共有状態がないため。
- 推論が容易: 隠れた何かが結果に影響しないため。
純粋 vs 不純
実際のプログラムには副作用(データの保存、APIの呼び出し)が必要なので、すべてを純粋にはできません。よくあるパターンは、純粋なロジックのコアを保ち、不純な作用を端へ押し出すことです。そこでは制御とテストがより容易になります。
純粋性と決定性
純粋関数は決定論的であり、これはまさに信頼できるテストが求めるものです。時計、ランダム性、ネットワーク呼び出しに依存するテストは不純で、flakyになりがちです。不純性を隔離することは、安定したテストスイートへの直接の道です。
手短な例
関数 add(a, b) => a + b は純粋です。同じ入力は常に同じ出力を返し、他の何にも触れません。Date.now() を返す関数は、出力が時間とともに変わるため不純です。
CIにおける純粋関数
純粋関数に対するテストは決定論的でめったにflakyにならないため、失敗はリトライすべきものではなく表面化する価値のある本物のバグです。Latchkeyは一時的なインフラ障害を自動でリトライしますが、本物で再現可能なテスト失敗は隠されないよう成立したままにします。
重要なポイント
- 純粋関数は同じ入力に対して同じ出力を返し、副作用を持たない。
- 純粋性はコードのテスト、cache化、並列化、推論を容易にする。
- 副作用を端に押し出すことで、決定論的でflakyの少ないテストスイートが得られる。