私はその言葉がかなり軽蔑的だと感じます。したがって、ウィキペディアの次の 2 つの文には驚かされます。
命令型プログラミングは、副作用を利用してプログラムを機能させることで知られています。関数型プログラミングは、副作用を最小限に抑えることで知られています。[1]
私はやや数学に偏っているので、後者は素晴らしいと思います。副作用の議論は何ですか?それは制御不能を意味するのか、それとも不確実性を受け入れることを意味するのか? 彼らは良いことですか?
私はその言葉がかなり軽蔑的だと感じます。したがって、ウィキペディアの次の 2 つの文には驚かされます。
命令型プログラミングは、副作用を利用してプログラムを機能させることで知られています。関数型プログラミングは、副作用を最小限に抑えることで知られています。[1]
私はやや数学に偏っているので、後者は素晴らしいと思います。副作用の議論は何ですか?それは制御不能を意味するのか、それとも不確実性を受け入れることを意味するのか? 彼らは良いことですか?
SO に関する質問をよく見かけますが、これは本当に悪いウィキペディアの記事を編集するのに 30 分も費やさなければなりません。記事は現在、適度に悪いだけです。あなたの質問に関連する部分で、私は次のように書きました:
コンピューター サイエンスでは、関数または式は、値を生成するだけでなく、何らかの状態を変更したり、呼び出し元の関数や外界との観察可能な相互作用を行ったりする場合に、副作用があると言われています。たとえば、関数は、グローバル変数または静的変数を変更したり、その引数の 1 つを変更したり、例外を発生させたり、データをディスプレイまたはファイルに書き込んだり、データを読み取ったり、他の副作用関数を呼び出したり、ミサイルを発射したりする場合があります。副作用がある場合、プログラムの動作は過去の履歴に依存します。つまり、評価の順序が重要です。効果的なプログラムを理解するには、考えられるすべての履歴を考慮する必要があるため、副作用によってプログラムが理解しにくくなることがよくあります。
プログラムが外部の世界 (人、ファイルシステム、ネットワーク上の他のコンピューター) と対話できるようにするには、副作用が不可欠です。ただし、副作用がどの程度使用されるかは、プログラミング パラダイムによって異なります。命令型プログラミングは、制御されていない無差別な副作用の使用で知られています。関数型プログラミングでは、副作用はめったに使用されません。Standard ML や Scheme などの関数型言語は副作用を制限していませんが、プログラマーはそれらを避けるのが通例です。関数型言語 Haskell は、静的型システムで副作用を制限します。IO タイプの結果を生成する関数だけが副作用を持つことができます。
副作用は必要悪であり、それらを最小限に抑える/ローカライズする必要があります。
スレッドに関する他のコメントは、効果のないプログラミングは直感的ではない場合があると述べていますが、人々が「直感的」と考えるものは、主に以前の経験の結果であり、ほとんどの人の経験には重度の命令的バイアスがあると思います. 主流のツールは日々ますます機能的になっています。なぜなら、人々は効果のないプログラミングがバグの減少につながることを発見しているからです (確かに、新しい/別の種類のバグが発生することもあります)。
パフォーマンスについて言及した人はほとんどいません。通常、エフェクトのないプログラミングは、効果のあるプログラミングよりもパフォーマンスが悪くなります。これは、コンピューターが (ラムダでうまく動作するように設計されているのではなく) エフェクトでうまく動作するように設計されているフォンノイマン マシンであるためです。マルチコア革命の真っ只中にいる今、パフォーマンスを向上させるためにコアを利用する必要があることに人々が気付くにつれて、これはゲームを変えるかもしれません.効果がない場合は、簡単に正しくできます。
von-Neumann マシンでは、副作用はマシンを機能させるものです。基本的に、プログラムをどのように作成しても、(低レベルのビューで) 動作させるには副作用を実行する必要があります。
副作用のないプログラミングとは、副作用を抽象化して、マシンの現在の状態を気にせずに問題を一般的に考え、プログラムのさまざまなモジュール (プロシージャ、クラスなど) 間の依存関係を減らすことを意味します。そうすることで、プログラムをより再利用しやすくなります (モジュールは動作する特定の状態に依存しないため)。
そうです、副作用のないプログラムは良いことですが、副作用はあるレベルでは避けられません (したがって、「悪い」とは見なされません)。
プロ:
短所:
たとえば、Haskell は、最初は非常にエレガントに見えますが、その後、外の世界をいじり始める必要があり、もはやあまり楽しくありません。(Haskell は状態を関数パラメーターとして移動し、それを Monads と呼ばれるものに隠します。これにより、命令型のそっくりなスタイルで記述できます。)
副作用がなければ、特定のことはできません。1 つの例は I/O です。メッセージを画面に表示することは、定義上、副作用であるためです。これが、副作用を完全に排除するのではなく、最小限に抑えることが関数型プログラミングの目標である理由です。
それはさておき、副作用を最小限に抑えることが、速度やメモリ効率などの他の目標と競合する場合がよくあります。また、状態を変更するというアイデアとよく一致する問題の概念モデルが既に存在し、その既存のモデルと戦うことは、エネルギーと労力の無駄になる可能性があります。
副作用は他の武器と同じです。それらは間違いなく有用であり、不適切に取り扱うと非常に危険になる可能性があります。
武器と同様に、さまざまな種類のさまざまな致死率の副作用があります。
C++ では、ポインターのおかげで、副作用はまったく制限されていません。変数が「プライベート」として宣言されている場合でも、ポインター トリックを使用してアクセスまたは変更できます。呼び出し元の関数のパラメーターやローカル変数など、スコープ外の変数を変更することもできます。OS (mmap) の助けを借りて、実行時にプログラムのマシン コードを変更することもできます。C++ のような言語で書くと、プロセス内のすべてのメモリのマスターであるビット ゴッドのランクに昇格します。コンパイラがコードに対して行うすべての最適化は、権限を乱用しないという前提で行われます。
Java では、あなたの能力はより制限されています。スコープ内のすべての変数は、異なるスレッドで共有される変数を含めて制御できますが、常に型システムに準拠する必要があります。それでも、自由に使える OS のサブセットと静的フィールドの存在のおかげで、コードはローカルではない影響を与える可能性があります。別のスレッドが何らかの方法で System.out を閉じると、魔法のように見えます。そして、それは魔法になります。副作用の魔法です。
Haskell には (純粋であるという宣伝にもかかわらず) IO モナドがあり、すべての副作用を型システムに登録する必要があります。コードを IO モナドにラップすることは、拳銃の 3 日間の待機期間のようなものです: それでも自分の足を吹き飛ばすことはできますが、政府に同意するまではできません。また、Haskell IO のブラック マーケットである unsafePerformIO とその同類もあり、「質問なし」の副作用をもたらします。
Haskell の前身である Miranda は、モナドが普及する前に作成された純粋関数型言語です。ミランダ (私が学んだ限りでは... 私が間違っている場合はラムダ計算に置き換えてください) には IO プリミティブがまったくありません。実行される唯一の IO は、プログラムのコンパイル (入力) とプログラムの実行、および結果の出力 (出力) です。ここでは、あなたは完全な純粋さを持っています。実行順序はまったく関係ありません。すべての「効果」は、それらを宣言する関数に対してローカルです。つまり、コードの 2 つのバラバラな部分が互いに影響を与えることは決してありません。それは(数学者にとって)ユートピアです。または同等のディストピア。それは退屈だ。何も起こりません。そのためのサーバーを作成することはできません。それにOSを書くことはできません。その中に SNAKE や Tetris を書くことはできません。誰もがちょっと座って数学的に見えます。
ここで何人かが言及しているように、副作用がなければ有用なアプリケーションを作成できないことは事実です。しかし、だからと言って、制御されていない方法で副作用を使用することが良いことであるということにはなりません.
次の類推を考えてみてください。分岐命令を持たない命令セットを備えたプロセッサは、まったく価値がありません。ただし、プログラマーが常にgotoを使用しなければならないというわけではありません。それどころか、構造化プログラミングや、Java のようなその後の OOP 言語は、goto ステートメントがなくても実行できることが判明し、誰もそれを見逃していませんでした。
(確かに、Java にはまだ goto があります。現在は、break、continue、およびthrowと呼ばれています。)
ほとんどのアプリケーションの大部分では、副作用が不可欠です。純粋関数には多くの利点があります。事前条件と事後条件を気にする必要がないため、考えやすくなります。状態を変更しないため、並列化が容易になります。これは、プロセッサ数が増えるにつれて非常に重要になります。
副作用は避けられません。そして、より複雑だが純粋なソリューションよりも優れた選択肢である場合はいつでも、それらを使用する必要があります。純関数も同様です。場合によっては、機能的なソリューションを使用して問題にアプローチする方が適切な場合があります。
それはすべて良いです =) 解決しようとしている問題に応じて、さまざまなパラダイムを使用する必要があります。
副作用がなければ、I/O 操作を実行できません。そのため、有用なアプリケーションを作成できません。
あなたのプログラムは何らかの出力や興味深い効果 (CPU の加熱は別として) を得るために副作用を持たなければならないので、問題はこれらの副作用がプログラムのどこで引き起こされるべきかということです。それらは、予期しないメソッドに隠されている場合にのみ有害になります。
経験則として: 純粋なメソッドと副作用のあるメソッドを分けてください。コンソールに何かを出力するメソッドは、それを行うだけで、他の場所で使用する可能性のある興味深い値を計算するべきではありません。
一つには、副作用を伴うプログラミングの方がはるかに簡単で直観的です。関数型プログラミングは、多くの人にとって理解しがたいものです。Ocaml のクラスを教えたり TA を取得したりした人を探してみると、おそらく、人々がそれを理解できなかったというあらゆる種類の話を聞くことになるでしょう。そして、誰も実際にそれに従うことができない場合、美しく設計され、驚くほど副作用のない関数コードを持っていても何のメリットがありますか? ソフトウェアを完成させるために人を雇うことはかなり難しくなります。
少なくとも、それは議論の 1 つの側面です。多くの人が関数型スタイル、副作用のないコードについてすべてを学ばなければならない理由はいくらでもあります。マルチスレッドが思い浮かびます。