関数の参照透過性と副作用についてのメモ
概要
プログラミング言語やUDF機能を提供するシステムにおいて関数が持っていると嬉しい性質として以下の2つがある。
- 関数の返す値は、関数の引数に与えた値だけに依存して決まる(参照透過性がある)。
- 関数は、値を返す以外の作用(副作用)を行わない(副作用がない)。
上記の2つの性質を持つ関数は、関数の処理系のコンパイラやオプティマイザによって最適化することができる。 例えば、
- 関数の引数が定数の場合、その関数の値(計算結果)をコンパイル時に決定できるので、実行コードにおける関数の呼び出しをなくすことができる。
- 同じ関数に同じ引数を与えている箇所があれば、2回目以降の関数の呼び出しをなくし、1回目の関数の計算結果に置き換えることができる。
関数に参照透過性がない場合
同じ関数に同じ引数を与えたとしても同じ結果を返すとは限らないため、コンパイラ/オプティマイザはその結果を事前計算orキャッシュすることができない。
参照透過性がない関数とは例えば、システム時刻を返す関数であったり、乱数によって振舞いを変えたり、通信やIO等の外部からの入力を利用したりするものである。
関数に副作用がある場合
副作用がある関数とは、例えば関数の実装の中でログに出力するような関数である。
そのため最適化によって関数呼び出しが削除されてしまうと、残るべきログが残らないという事態が起こる。
参照透過性と副作用のイメージ
関数の引数(INPUT)と、関数の計算結果(OUTPUT)による界面(インターフェイス)を通常と考えると、それ以外の界面からの入力があると参照透過性を脅かし、それ以外の界面への出力があると副作用となりうる。
参照透過性と副作用の関係
両者は独立して存在しうる。
参照透過性があるが副作用を持つ関数も存在するし、副作用はないが参照透過性がない関数もありうる。
参照透過性と副作用は本当に独立か?
副作用がない関数がありうると書いたが、実際には何を副作用とみなすかは程度問題である。
ログ出力は典型的な副作用であるが、ログが最適化で省略されても問題ないのであれば、その副作用は無視できる。
また世の中にはサイドチャネル攻撃というものが存在するので、ふつうに考えて副作用がないような関数も、同じコアで同時実行される別のスレッドのスケジューリングやCPUの電力消費や電波の輻射等の形で外部に影響を与えるため、これを副作用とみなすこともできる。
その意味で副作用とは、その副作用を観測するものの存在ぬきでは語れない。
参照透過性のない関数は、そのような副作用を観測するものの一つである。