関数型プログラミング言語は副作用を許可しませんか?


10

ウィキペディアによると、宣言である関数型プログラミング言語は、副作用を許可していません。一般に宣言型プログラミングでは、副作用を最小限に抑えたり、排除したりします。

また、ウィキペディアによると、副作用は状態変化に関連しています。したがって、関数型プログラミング言語は、その意味では、状態を保存しないため、実際には副作用を排除します。

しかし、それに加えて、副作用には別の定義があります。副作用

値を返す以外に、呼び出し側の関数または外界との観測可能な相互作用があります。たとえば、特定の関数は、グローバル変数または静的変数の変更、その引数の1つの変更、例外の発生、ディスプレイまたはファイルへのデータの書き込み、データの読み取り、または他の副作用のある関数の呼び出しを行う場合があります。

その意味で、関数型プログラミング言語は実際には副作用を許容します。外部の世界に影響を及ぼし、他の関数を呼び出し、例外を発生させ、ファイルに書き込むなどの無数の例があるからです。

それで、最後に、関数型プログラミング言語は副作用を許可するかどうか?

または、何が「副作用」とみなされるか理解していないため、命令型言語では許可され、宣言型言語では許可されません。上記と私が得た結果によると、副作用を排除する言語はないため、副作用について何か見落としているか、ウィキペディアの定義が誤っています。

回答:


26

関数型プログラミングには、さまざまな手法が含まれます。いくつかのテクニックは副作用で問題ありません。しかし、1つの重要な側面は方程式の推論です。同じ値で関数を呼び出しても、常に同じ結果が得られます。そのため、関数呼び出しを戻り値に置き換えて、同等の動作を得ることができます。これにより、特にデバッグの際に、プログラムについての推論が容易になります。

関数に副作用がある場合、これは完全には成立しません。戻り値には副作用が含まれていないため、戻り値は関数呼び出しと同等ではありません。

溶液は、使用を中止することにある側面の効果を、これらの影響をコード戻り値に。言語によってエフェクトシステムは異なります。たとえばHaskellはモナドを使用して、IOやState変異などの特定の効果をエンコードします。C / C ++ / Rust言語には、一部の値の変更を許可しない型システムがあります。

命令型言語では、print("foo")関数は何かを出力して何も返しません。Haskellのような純粋な関数型言語では、print関数は外界の状態を表すオブジェクトも取り、この出力を実行した後の状態を表す新しいオブジェクトを返します。のようなものnewState = print "foo" oldState。古い状態から好きなだけ新しい状態を作成できます。ただし、メイン関数で使用されるのは1つだけです。そのため、関数を連鎖させることにより、複数のアクションから状態を順序付ける必要があります。印刷するためにfoo bar、私は次のようなものを言うかもしれませんprint "bar" (print "foo" originalState)

出力状態が使用されない場合、Haskellは遅延言語であるため、その状態に至るまでのアクションを実行しません。逆に、この遅延はすべての効果が戻り値として明示的にエンコードされているためにのみ可能です。

Haskellは、このルートを使用する唯一の一般的に使用される関数型言語であることに注意してください。その他の関数型言語 Lispファミリ、MLファミリ、およびScalaなどの新しい関数型言語は推奨しませんが、副作用を許容します。これらは命令型関数型言語と呼ばれる可能性があります。

I / Oに副作用を使用することはおそらく問題ありません。多くの場合、I / O(ロギング以外)はシステムの外側の境界でのみ行われます。ビジネスロジック内で外部通信は発生しません。そうすれば、純粋なスタイルでソフトウェアのコアを記述しながら、外部シェルで不純なI / Oを実行できます。これは、コアがステートレスになる可能性があることも意味します。

ステートレスには、合理性とスケーラビリティの向上など、多くの実用的な利点があります。これは、Webアプリケーションのバックエンドで非常に人気があります。状態はすべて、共有データベース内の外部に保持されます。これにより、負荷分散が容易になります。セッションを特定のサーバーに固定する必要はありません。さらにサーバーが必要な場合はどうなりますか?同じデータベースを使用しているため、もう1つ追加してください。1つのサーバーがクラッシュした場合はどうなりますか?別のサーバーで保留中の要求をやり直すことができます。もちろん、データベースにはまだ状態があります。しかし、それを明示的にして抽出したので、必要に応じて、純粋な機能的アプローチを内部で使用することもできます。


詳細な回答ありがとうございます。私が結論として保持しているのは、方程式の推論により副作用が関数値に影響を与えないということです。これが「関数型言語が副作用を許可/最小化しない」理由です。関数値に埋め込まれた効果は、これまでに保存された状態、またはプログラムのコアの外側に保存された状態に影響を与え、変更します。また、I / Oはビジネスロジックの外側の境界で発生します。
codebot

3
@codebot、私はそうは思いません。適切に実装されている場合、関数型プログラミングの副作用は関数の戻り値の型に反映されます。たとえば、関数が失敗する可能性がある場合(特定のファイルが存在しないか、データベース接続を確立できない場合)、関数に例外をスローさせるのではなく、関数の戻り値の型で失敗をカプセル化する必要があります。例として、鉄道指向プログラミングを見てください(fsharpforfunandprofit.com/posts/recipe-part2)。
アーロンM.エシュバッハ2018

「...それらは命令型関数型言語と呼ばれることもある。」:Simon Peyton Jonesは「... Haskellは世界で最も優れた命令型プログラミング言語です」と書いています。
ジョルジオ

5

副作用を排除するプログラミング言語はありません。宣言型言語には副作用が含まれているのに対し、命令型言語には副作用がないと言った方がいいと思います。ただし、副作用に関するこの話のいずれかが2種類の言語の根本的な違いを理解しているかどうかはよくわかりません。

例で違いを説明するのに役立つと思います。

a = b + c

上記のコード行は実質的に任意の言語で記述できるため、命令型言語と宣言型言語のどちらを使用しているかをどのように判断できますか?そのコード行のプロパティは、2つの言語クラスでどのように異なりますか?

命令型言語(C、Java、Javascriptなど)では、そのコード行はプロセスのステップを表すだけです。それは、値の基本的な性質について何も教えてくれません。このコード行の後(次の行の前)aは現時点でbプラスに等しいとc言えますaが、より広い意味では何もわかりません。

宣言型言語(Haskell、Scheme、Excelなど)では、そのコード行はさらに多くのことを示しています。これは、間の不変関係を確立aし、他の2つは、それがするようなオブジェクトを常にそうでaに等しいb加えましたc。注、場合でも、ので、私は宣言型言語の一覧でExcelが含まれていることbか、c値を変更し、実際にはまだそれが残っているだろうa彼らの和に等しくなります。

私の考えでは、これは副作用や状態ではなく、2種類の言語を異なるものにするものです。命令型言語では、特定のコード行は、問題の変数の全体的な意味について何も伝えません。言い換えれば、a = b + c時間だけで非常に簡単な瞬間のために、という意味aの合計に等しくなるように起こったbc

一方、宣言型言語では、コードのすべての行が、プログラムの存続期間全体にわたって存在する基本的な真実を確立します。これらの言語でa = b + cは、他のコード行で何が発生しても、a常にとの合計に等しくbなりcます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.