副作用 (プログラム)
プログラミングにおける副作用(ふくさよう)とは、ある機能がコンピュータの(論理的な)状態を変化させ、それ以降で得られる結果に影響を与えることをいう。代表的な例は変数への値の代入である。
例えば与えられた数字を二倍して返す機能"double"があるとする。
double: x -> 2*x 例: 4 <- double: 2
このような機能では次のことが成立する。
- 同じ条件を与えれば必ず同じ結果が得られる
- 他のいかなる機能の結果にも影響を与えない
このような性質を参照透過性という[1]。参照透過な機能はそれ自身状態を持たないことで副作用と独立している。
一方状態を持つ機能"add"を考える。addは外側の変数eを増加させて返すものとすれば:
add: x -> e:e+x 例: e: 1 2 <- add:1 2 <- e ...
のようになるだろう。このような機能では見えない所で条件を変化させてしまうために、参照透過性の一つ目の仮定が崩れ、また他のeを利用する機能の結果も変化させるので二つ目の仮定も成立しない。addは副作用を持つ機能である。
副作用を伴う機能の例としては、I/O制御(write/print等)、上記addのように破壊的代入を行う機能などがある。ノイマン型のアーキテクチャは副作用を前提として動作するため、多くのプログラミング言語では変数の破壊的代入機能を持つ。一方関数型言語では原則として副作用を存在しないものとみなし、モナドなどの手法で抽象化している。
機能が副作用を持たないことの利点は、いかなる状況でも常に同じ結果が得られるために、機能を純粋に宣言型プログラミングで定義でき、状況依存でのバグの発生が抑えられるということである[2]。反面副作用を持たない言語設計はノイマン型アーキテクチャと反りが合わず、効率の点で不利になることが多い。また単純な逐次処理を行う場合は状態を中心に命令的な思考をした方が扱いやすい場合がある。このためLISPやMLなどは原則として関数型ながら、副作用を許容する設計になっている。
脚注
- ↑ 厳密に言えば計算途上でコンピュータの物理的状態は変化しているし、OSやメモリ状態といった他のレベルでは参照透過性は崩れているかもしれない。しかし「この機能が考える世界」にはそのような変化は存在しないものとして仮想化される。
- ↑ ダイクストラは構造化プログラミングでほぼ同等の主張を述べている。副作用の問題は関数型言語に特有というわけではない。