「副作用」とは何ですか?


87

副作用の概念を明確に理解していません。

  • プログラミングの副作用とは何ですか?
  • プログラミング言語に依存していますか?
  • 外部および内部の副作用などがありますか?

副作用を引き起こす原因の例をいくつか挙げてください。


7
宿題のように聞こえます。
gnasher729

3
これを気にする@ gnasher729は非常に便利です:)
チャーリーパーカー

回答:


108

副作用は単に状態のいくつかの種類の修飾を意味する-例えば:

  • 変数の値を変更する;
  • 一部のデータをディスクに書き込む。
  • ユーザーインターフェイスでボタンを有効または無効にします。

一部の人々が言っ​​ているように思われることに反して:

  • 副作用を隠したり予期したりする必要はありませ(可能性はありますが、コンピューターサイエンスに適用される定義とは関係ありません)。

  • 副作用は、dem等性とは関係ありません。べき等関数には副作用があり、非べき等関数には副作用がない場合があります(現在のシステムの日付と時刻の取得など)。

とても簡単です。副作用=どこかで何かを変える。

PSコメンテーターのベンジョルが指摘するように、何人かの人々は副作用の定義を純粋な関数の定義と混同しているかもしれません。一般的なコンピューターサイエンスでは、一方は他方を意味しませんが、関数型プログラミング言語は通常、両方の制約を強制する傾向があります。


38
句「副作用」は何かのようにそれが聞こえる他には意図されたもの以外に変更されています。薬では、薬は痛みを和らげる主な効果があり、時には鼻出血、めまいなどの副作用があります。薬の目的は鼻出血を引き起こすことではなく、意図しない余分な結果。
FrustratedWithFormsDesigner

15
@Frustrated:+1。その用語を見たときはいつでも、FP支持者がその微妙に不吉な意味合いを正確に作成するために選ばれたのではないかと疑問に思わずにはいられません。
メイソンウィーラー

6
@メイソン・ウィーラー。FPよりもずっと前から存在していました。そして、それは微妙に不吉な意味合いではありません。それは完全な悪であり、常にそうでした。私がコーディングしてきた30年間、「暗号割り当て」ステートメント-副作用-は人々を悩ませてきました。単純な古い代入文は、はるかに簡単に対処できます。
S.Lott

7
@Mason Wheeler:Cで++a。割り当てのように見えません。 b = ++a;2つの副作用があります。明白なものとの暗号割り当てa。それは、(ある人にとっては)望ましい副作用のようなものです。しかし、私のキャリア全体が微妙にならないようにするための副作用と呼ばれています。
-S.Lott

5
@Zachary、私の答えの最後の箇条書きをご覧ください。あなたが言及しているのは、べき等の振る舞い(またはその欠如)です。副作用については何もわかりません。システムクロックの確認は副作用ではありません。実際、「get」という語が前に付いている関数またはメソッドは、副作用がないと合理的に期待するものです。
アーロンノート

36

コンピューターの状態を変更したり、外の世界とやり取りしたりする操作には、副作用があると言われています。副作用に関するウィキペディアを参照してください。

たとえば、この関数には副作用はありません。その結果は入力引数のみに依存し、呼び出されたときにプログラムの状態や環境は変化しません。

int square(int x) { return x * x; }

対照的に、これらの関数を呼び出すと、コンピューターの状態に関する何かが変わるため、呼び出した順序に応じて異なる結果が得られます。

int n = 0;
int next_n() { return n++; }
void set_n(int newN) { n = newN; }      

この関数には、出力にデータを書き込むという副作用があります。戻り値が必要なため、関数を呼び出さないでください。「外の世界」に与える効果が必要なため、これを呼び出します。

int Write(const char* s) { return printf("Output: %s\n", s); }

1
これは良い定義ですが、詳細については夢中ではありません-Thorbjørnの答えのように、その一部は副作用の問題をi等関数の問題と混同しているようです。あなたのWrite例が示すように、副作用があるということは、関数がその入力に対してその出力を変更すること、あるいはその出力が入力にまったく依存することさえ意味しません。
アーロンノート

6
それはdem等であることではありません。出力を生成するという事実は、副作用があることを意味します。
クリストファージョンソン

一部のシステムでは、呼び出しsquare(x)により、関数が定義されているモジュールがディスクからロードされる場合があります。これを副作用と見なすべきですか?結局のところ、これは(最初の)呼び出しはなど、そのRAMの使用率が上がる、予想外に時間がかかることをメンズ
ハーゲン・フォン・Eitzen

1
@HagenvonEitzenすべての操作は、実際にコンピューターの状態(CPUレジスター、メモリー、電力消費、熱など)を変更します。「サイド効果」とは通常、プログラムが明示的に変更しない限り、その環境について何も変わらない想像上の理想的な実行環境を指します。ただし、外部コンピューターの状態を変更するsquare(x) ために呼び出している場合、それは副作用であると考えることができます。
クリストファージョンソン

私にとって最初のイラストは完璧に理にかなっています。ただし、2番目はそうではありません。特定の環境/スコープに関連して副作用を定義する必要があると思います。宇宙全体を考慮すると、副作用がないということはありません。コンピュータに限定しても、CPUが同じように動作しないという点で、関数は別のプロセスに影響します。スコープをローカル関数スコープでアクセス可能なものに限定すると、話をすることができます。
funct7

20

既存の答えはかなり良いと思います。IMOが十分に強調されていないいくつかの側面について詳しく説明したいと思います。

数学では、関数は値のタプルから値への単なるマッピングです。だから、機能与えられたfxf(x)常に同じ結果になりますy。あなたも交換することができるf(x)とのy表現にどこでも、何も変更されます。

多くのプログラミング言語で関数(またはプロシージャ)と呼ばれるものは、次の理由で実行できる構造(コード)です。

  1. 数学的な意味で関数を計算します。つまり、入力値が与えられると、結果を返します。
  2. 画面に何かを印刷したり、データベースの値を変更したり、ミサイルを発射したり、10秒間スリープしたり、SMSを送信したりするなど、何らかの効果があります。

そのため、効果は状態だけでなく、ミサイルの発射や数秒間の実行の一時停止などの他の側面にも関連します。

副作用という用語は否定的に聞こえるかもしれませんが、通常、関数を呼び出すことの効果は関数自体の目的です。I用語関数は元々値があると考えられている計算、数学で使用されたため、と仮定一次効果の任意の他の効果が考慮されるのに対し、関数の副作用。一部のプログラミング言語では、手続きという用語を使用して、数学的な意味での関数との混同を避けています。

ご了承ください

  1. 一部のプロシージャは、戻り値と副作用の両方に役立ちます。
  2. 一部のプロシージャは結果値のみを計算し、他の効果はありません。数学的な意味で関数を計算するだけなので、しばしば純粋関数と呼ばれます。
  3. sleep()Pythonなどの一部のプロシージャは、その(副作用)効果に対してのみ有用です。これらは多くの場合、特別な値Noneunitまたは()または... を返す関数としてモデル化されます。これは、計算が正しく終了したことを単に示します。

2
私の謙虚な意見では、これは受け入れられた答えであるべきです。副作用の概念は、数学関数の観点からも意味があります。プロシージャは、一連の命令を構造化された方法で単純にグループ化すると同時に、どこからでも簡単にそのセットにジャンプできるように設計されています。主な意図した効果と副次的な効果はありません。例外をスローすることは、プロシージャの副作用であると言うことができます。プロシージャの意図を中断して、中断した場所に戻り、そこで実行フォームを続行することです。
ディディエA.

4

副作用とは、操作が意図した使用範囲外の変数/オブジェクトに影響を与える場合です。

グローバル変数を変更する副作用がある複雑な関数を呼び出すと、それが呼び出された理由ではありませんでした(データベースから何かを抽出するために呼び出された可能性があります)。

私は完全に不自然に見えない簡単な例を思い付くのに苦労していることを認めます、そして私が取り組んだものからの例はここに投稿するには長すぎます(そしてそれは仕事に関連しているので、とにかくおそらく)。

(少し前に)見た例の1つは、接続が閉じた状態にある場合にデータベース接続を開く関数でした。問題は、関数の最後で接続を閉じることになっていたが、開発者はそのコードを追加するのを忘れていたということでした。そのため、ここでは意図しない副作用がありました:プロシージャの呼び出しはクエリのみを行うことになっており、副作用は接続が開いたままであり、関数が続けて2回呼び出された場合、接続があったというエラーが発生しますすでに開いています。


OK、だからみんなが今例を挙げているので、私もそう思うと思う;)

/*code is PL/SQL-styled pseudo-code because that's what's on my mind right now*/

g_some_global int := 0; --define a globally accessible variable somewhere.

function do_task_x(in_a in number) is
begin
    b := calculate_magic(in_a);
    if b mod 2 == 0 then
        g_some_global := g_some_global + b;
    end if;
    return (b * 2.3);
end;

機能はdo_task_x有している一次いくつかの計算の結果を返すの効果、及びサイドおそらくグローバル変数を変更することの効果を。

もちろん、どちらが主であり、どちらが副作用であるかは、解釈の余地があり、実際の使用法に依存する可能性があります。グローバルを変更する目的でこの関数を呼び出し、返された値を破棄すると、グローバルの変更が主な効果であると言えます。


2
これが良い普遍的な定義だとは思いません。多くのプログラマーは、特に副作用のために意図的に構造を使用します。
CBベイリー

@チャールズ:結構です。その場合、どのように定義しますか?
FrustratedWithFormsDesigner

2
@KristopherJohnsonには最も明確な定義があると思います。プログラムまたはその環境の状態を変更したり、出力の生成などの現実世界の効果を生み出すもの。
CBベイリー

@チャールズベイリー:それは定義を変更しません。副作用のために物を使うのは問題ありません。副作用があることを理解している限り。この定義については何も変わりません。
-S.Lott

1
@SLott:この回答の定義(つまり、最初の段落)には、「意図された使用法以外」という節が含まれています。私のコメントは公平だったと思います。
CBベイリー

3

コンピューターサイエンスでは、関数または式は、何らかの状態を変更したり、呼び出し元の関数または外部の世界と観察可能な相互作用がある場合に副作用があると言われています。

ウィキペディアから-副作用

関数は、数学的な意味で、入力から出力へのマッピングです。関数を呼び出すことの意図された効果は、それが返す出力に入力をマップすることです。関数が他に何かを行う場合、それは問題ではありませんが、入力を出力にマッピングしない動作がある場合、その動作は副作用であることが知られています。

より一般的な用語では、副作用は、構造の設計者の意図した効果ではない効果です。

効果は、俳優に影響を与えるものです。ガールフレンドに別れたテキストメッセージを送信する関数を呼び出すと、それは多くの俳優、私、彼女、携帯電話会社のネットワークなどに影響を及ぼします。入力からマッピングを返します。だから:

   public void SendBreakupTextMessage() {
        Messaging.send("I'm breaking up with you!")
   }

これが関数であることを意図している場合、すべきことはvoidを返すことだけです。副作用がない場合は、実際にテキストメッセージを送信しないでください。

ほとんどのプログラミング言語では、数学関数の構成体はありません。そのようなものとして使用することを意図した構造はありません。それが、ほとんどの言語でメソッドまたはプロシージャがあると言われている理由です。設計上、これらはより多くの効果を実行できるように意図されています。一般的なプログラミング用語では、メソッドやプロシージャが何であるかを実際に気にする人はいないので、誰かがこの関数に副作用があると言うと、事実上、このコンストラクトは数学関数のように動作しません。そして、この関数には副作用がないと誰かが言うと、この構造は数学関数のように効果的に動作します。

定義により、純粋な関数には常に副作用がありません。純粋な関数とは、この関数は、より多くの効果を可能にする構造を使用しているにもかかわらず、数学的な関数の効果と同等の効果しか持たないということです。

副作用のない関数が純粋ではない場合、私は誰にでも言うように挑戦します。純粋で副作用のない用語を使用した文のコンテキストの主な意図された効果が、関数の数学的な意図された効果のものでない限り、それらは常に等しい。

そのため、まれではありますが、これは受け入れられた答えの中で人々に欠けている、誤解している(最も一般的な仮定ではない)区別であると信じていますが、プログラミング関数の意図された効果は入力を出力にマップします。入力は関数の明示的なパラメーターに制約されませんが、出力は明示的な戻り値に制約されます。それが意図された効果であると仮定した場合、ファイルを読み込んでファイルの内容に基づいて異なる結果を返す関数は、意図した効果の他の場所からの入力を許可するため、副作用がありません。

では、なぜこれがすべて重要なのでしょうか?

それはすべて制御とそれを維持することです。関数を呼び出し、何か他のことをしてから値を返す場合、その動作について推論するのは困難です。実際のコードの関数内を調べて、何をしているかを推測し、その正確性を主張する必要があります。理想的な状況は、関数が使用している入力が何であるかを非常に明確かつ簡単に知ることができ、それに対する出力を返すこと以外は何も実行しないことです。これを少しリラックスして、それが使用している入力を正確に知ることは、値を返すことに気付いていないかもしれない他のことを何もしていないことを確信するほど有用ではないと言うことができます。それは、それをどこから取得したかに関係なく、入力にマップする他のことは何もしないということです。

ほとんどすべての場合、プログラムのポイントは、入ってくるものを出てくるものにマッピングする以外の効果を持つことです。副作用を制御するという考え方は、理解しやすく、推論しやすい方法でコードを整理できるということです。非常に明示的かつ中心的な場所ですべての副作用をまとめると、これが起こっているのはこれだけであるので、どこを見れば信頼できるかが簡単にわかります。入力も非常に明示的である場合、異なる入力の動作をテストするのに役立ち、多くの異なる場所で入力を変更する必要がないため、使いやすいです。欲しいものを手に入れます。

プログラムの動作を理解、理由、制御するのに最も役立つのは、すべての入力を明確にグループ化して明示的にすることと、すべての副作用を一緒にグループ化して明示的にすることです。副作用、純粋など

最も有用なのは副作用とその明示性のグループ化であるため、人々はそれを意味するだけで、純粋ではないが「副作用」がないと言って区別する場合があります。しかし、副作用は、想定される「意図された主効果」に関連しているため、文脈上の用語です。これは、あまり頻繁には使用されていませんが、驚くことに、このスレッドでは多くのことについて語られています。

最後に、i等性とは、同じ入力を使用してこの関数を何度も呼び出すと(それらがどこから来たかは関係ありません)、常に同じ効果(副作用の有無)が発生することを意味します。


副作用を説明する際の大きな問題は、OcamlやHaskellのような言語を使用するまでは、副作用のない(ほぼ!)プログラミングについて推論するのが非常に難しいことです。
ジェイミーシュトラウス

2

プログラミングでは、プロシージャが変数をそのスコープ外から変更するときの副作用があります。副作用は言語に依存しません。副作用を排除することを目的とする言語のクラス(純粋な関数型言語)がありますが、副作用が必要なものがあるかどうかはわかりませんが、間違っている可能性があります。

私の知る限り、内部および外部の副作用はありません。


より正確に言うと、純粋な関数型言語は、副作用のないコードを他のコードから明確に分離しますが、他の言語には純粋なコードと不純なコードを区別するメカニズムがありません。ほとんどのプログラムには、何らかの副作用がある必要があります。
ジョルジオ14年

MS-BASICやQBasicのようないくつかのpre-guiプログラミング言語は、「副作用のみ」の言語にできるだけ近いものだったと思います。そして、はい、あなたは内部と外部の両方の副作用を持つことができます。
ジェームズK

0

以下に簡単な例を示します。

int _totalWrites;
void Write(string message)
{
    // Invoking this function has the side effect of 
    // incrementing the value of _totalWrites.
    _totalWrites++;
    Debug.Write(message);
}

副作用の定義はプログラミングに固有のものではないので、単にあなたの薬の副作用や食べ過ぎの副作用を想像してください。


ただし、メッセージが参照として受信され、メソッドでメッセージを変更すると、副作用が生じる可能性があります。私は正しいですか?
アミールRezaei

x++が変数を変更するという事実は、x一般的に副作用と見なされます。式の値は、のプリインクリメント値ですx。これは、式の副作用以外の部分です。
CBベイリー

@Charles-同意しますが、元の例は現在の例ほど明確ではありませんでした。
ChaosPandion

@Amir-まあ、それは本当に言語に依存します。これがC#の場合、これは副作用とは見なされません。
ChaosPandion

@ChaosPandion:個人的には同意しません。元の例は、はるかに単純で明確でした。
CBベイリー

-2

副作用は、コード内で発生するもので、明らかではありません。

たとえば、このクラスがあるとしましょう

public class ContrivedRandomGenerator {
   public int Seed { get; set; }

   public int GetRandomValue()
   {
      Random(Seed);
      Seed++;
   }
}

最初にクラスを作成するときに、シードを与えます。

var randomGenerator = new ContrivedRandomGenerator();
randomGenerator.Seed = 15;
randomGenerator.GetRandomValue();

内部を知らず、ランダムな値を取得することを期待し、randomGenerator.Seedがまだ15であることを期待しますが、そうではありません。

関数呼び出しには、シード値を変更する副作用がありました。


10
副作用を隠す必要はありません。あなたは口語または医学的使用を考えています。プログラミングでは、副作用は単に状態を変更することを指します。
アーロンノート

1
コンソールへの印刷は副作用です。隠されていません。ウィキペディアから:「コンピューターサイエンスでは、関数または式は、値を返すことに加えて、状態を変更したり、呼び出し元の関数または外部世界と観察可能な相互作用がある場合、副作用があると言われています。」

副作用は、非関数(つまり、プロシージャ)がどのように作業を完了するかです。X = 1; X = Y(10)は2つの純粋な関数です。「x =何でも」レルムの外に出たとき、画面に出力を書き込む| drive | printer | ledまたは「x = y」形式以外の入力を読み取るか、単に変数の値を別のものに変更するか、それは副作用です。
ジェームズK

「隠された」とは、彼が非自明であることを意味すると思います。x = f(y、z)のように、xはyとzに基づいていると仮定できます。一方、proc(x、y、z)は何が起こっているかについて何も伝えません。すべての変数を変更することも、変更しないこともできます。Procはfに類似したものでも、まったく関係のないものでもかまいません。純粋関数には、「x」という単一の答えがあります。それを超えて、それは副作用です。完全に意図しているが、副作用。
ジェームズK

0を理解するのと同じように、まず1を理解する必要があります。副作用を理解するには、最初に機能を理解する必要があります。
ジェームズK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.