コンピュータサイエンスでは、操作、関数、または式が、引数の値を読み取って操作の呼び出し元に値を返すという主な効果以外の観察可能な効果を持つ場合、副作用があると言われます。副作用の例には、非ローカル変数、静的ローカル変数、参照渡しされる可変引数の変更、エラーまたは例外の発生、 I/Oの実行、副作用のある他の関数の呼び出しなどがあります。[1]副作用がある場合、プログラムの動作は履歴に依存する場合があります。つまり、評価の順序が重要になります。副作用のある関数を理解してデバッグするには、コンテキストとその可能性のある履歴に関する知識が必要です。[2] [3] 副作用は、プログラミング言語の設計と分析において重要な役割を果たします。副作用が使用される程度は、プログラミングパラダイムによって異なります。たとえば、命令型プログラミングは、副作用を生成してシステムの状態を更新するためによく使用されます。対照的に、宣言型プログラミングは、副作用なしでシステムの状態を報告するためによく使用されます。
関数型プログラミングは副作用を最小限に抑える、または排除することを目的としています。副作用がないと、プログラムの形式検証が容易になります。関数型言語Haskellは、I/Oやその他の状態のある計算などの副作用をモナドアクションに置き換えることで排除します。[4] [5] Standard ML、Scheme、Scalaなどの関数型言語では副作用を制限していませんが、プログラマは副作用を避けるのが慣例となっています。[6]
効果システムは、効果を追跡するために型を拡張し、副作用の範囲と性質に関する情報を維持しながら、効果のある関数の簡潔な表記を可能にします。特に、効果のない関数は純粋関数に対応します。
アセンブリ言語プログラマは、隠れた副作用、つまり命令のニーモニックには記載されていないプロセッサ状態の一部を変更する命令に注意する必要があります。隠れた副作用の典型的な例は、レジスタを明示的に変更する (意図された効果)一方で条件コードを暗黙的に変更する (隠れた副作用) 算術命令です。隠れた副作用のある命令セットの潜在的な欠点の 1 つは、多くの命令が条件コードなどの単一の状態に対して副作用を持つ場合、その状態を順番に更新するために必要なロジックがパフォーマンスのボトルネックになる可能性があることです。この問題は、パイプライン(1990 年以降) またはアウトオブオーダー実行で設計された一部のプロセッサで特に深刻です。このようなプロセッサでは、隠れた副作用を検出し、次の命令がそれらの副作用の結果に依存する場合にパイプラインを停止するための追加の制御回路が必要になる場合があります。
参照の透明性
副作用がないことは参照透過性の必要条件ですが、十分条件ではありません。参照透過性とは、式 (関数呼び出しなど) をその値に置き換えることができることを意味します。そのためには、式が純粋であること、つまり、式が決定論的(同じ入力に対して常に同じ値を返す) かつ副作用がないことが条件となります。
一時的な副作用
操作の実行にかかる時間によって生じる副作用は、副作用と参照透過性について議論する際には通常無視されます。ハードウェアのタイミングやテストなど、一時的な副作用のために操作が挿入される場合もあります (例:sleep(5000)または ) for (int i = 0; i < 10000; ++i) {}。これらの命令は、完了するまでに時間がかかること以外は状態を変更しません。
冪等性
副作用のあるサブルーチンは、サブルーチンを複数回適用した場合、システム状態に対して 1 回の適用と同じ効果がある場合、つまり、サブルーチンに関連付けられたシステム状態空間からそのサブルーチン自体への関数が数学的な意味でべき等である場合に、べき等です。たとえば、次のPythonプログラムを考えてみましょう。
x = 0
def setx ( n ):
グローバル x
x = n
setx ( 3 )
アサート x == 3
setx ( 3 )
アサート x == 3
setxsetxは、 を 3 に 2 回目に適用すると、システム状態に対して最初の適用と同じ効果が生じるため、べき等です。xは、最初の適用後にすでに 3 に設定されており、2 回目の適用後も 3 に設定されています。
純粋関数は、数学的な意味でべき等である場合、べき等です。たとえば、次の Python プログラムを考えてみましょう。
def abs ( n ):
n < 0の場合は- nを返し 、そうでない場合はn
abs ( abs ( - 3 )) == abs ( - 3 )をアサートする
absabsはべき等です。これは、-3 への最初の適用の戻り値への
2 回目の適用が、-3 への最初の適用と同じ値を返すためです。
例
副作用の動作の一般的な例として、Cの代入演算子が挙げられます。代入は式 と同じ値に評価される式であり、副作用として のR 値をのL 値に格納します。これにより、複数の代入が可能になります。
a = bbba
a = ( b = 3 ); // b = 3 は 3 と評価され、それが a に割り当てられます
演算子は右結合するので、これは次と同等である。
a = b = 3 ;
これは初心者プログラマーにとって潜在的な障害となり、
while ( b == 3 ) {} // b が 3 と評価されるかどうかをテストします
と
while ( b = 3 ) {} // b = 3 は 3 と評価され、それが true にキャストされるため、ループは無限になります。
参照
参考文献
- ^ Spuler, David A.; Sajeev, A. Sayed Muhammed (1994 年 1 月).コンパイラによる関数呼び出しの副作用の検出. James Cook University . CiteSeerX 10.1.1.70.2096 .
副作用という用語は、非ローカル環境の変更を指します。通常、これは関数 (またはプロシージャ) がグローバル変数または参照パラメーターによって渡される引数を変更するときに発生します。ただし、非ローカル環境が変更される他の方法もあります。関数呼び出しによる副作用の原因として、次のことが考えられます。1. I/O の実行。2. グローバル変数の変更。3. ローカル永続変数 (C の静的変数など) の変更。4. 参照によって渡される引数の変更。5. 関数呼び出しシーケンスの上位にある関数のローカル変数 (自動または静的) の変更 (通常はポインタ経由)。
- ^ Turner, David A.編 (1990).関数型プログラミングの研究トピック. Addison-Wesley . pp. 17–42.Hughes, Johnより。「関数型プログラミングが重要な理由」(PDF)。2022 年 6 月 14 日のオリジナルからアーカイブ(PDF) 。2022 年 8 月 6 日に取得。
- ^ Collberg, Christian S. (2005-04-22). 「CSc 520 プログラミング言語の原則」アリゾナ大学コンピューターサイエンス学部。2022年8月6日時点のオリジナルよりアーカイブ。2022年8月6日閲覧。
- ^ 「Haskell 98 レポート」 1998 年。
- ^ Jones, Simon Peyton; Wadler, Phil (1993).命令型関数型プログラミング. 第20回ACMプログラミング言語原理シンポジウム会議記録. pp. 71–84.
- ^ Felleisen, Matthias ; Findler, Robert Bruce; Flatt, Matthew; Krishnamurthi, Shriram (2014-08-01). 「How To Design Programs」(第2版). MIT Press .
