回答:
不変データオブジェクトを操作する場合、関数には、同じ入力で呼び出すたびに同じ出力を生成するプロパティがあります。これにより、計算の概念化と正しい計算が容易になります。また、テストが容易になります。
それはほんの始まりです。数学は長い間関数を扱ってきたので、数学から借りることができる多くの推論手法があり、それらをプログラムに関する厳密な推論に使用します。私の観点からの最も重要な利点は、機能プログラムの型システムが十分に開発されていることです。そのため、どこかでミスをすると、型の不一致として現れる可能性が非常に高くなります。したがって、型付き関数型プログラムは、命令型プログラムよりもはるかに信頼性が高い傾向があります。
対照的に、可変データオブジェクトを操作する場合、最初に、計算中にオブジェクトが通過する複数の状態を記憶および管理するという認知的な負荷があります。特定のステップに必要なすべてのプロパティがその時点で満たされるように、正しい順序で物事を行うように注意する必要があります。間違いを犯すのは簡単であり、型システムはそれらの間違いをキャッチするほど強力ではありません。
数学は、変更可能なデータオブジェクトでは機能しませんでした。したがって、それらから借用できる推論手法はありません。コンピューターサイエンスで開発された独自の手法、特にFloyd-Hoare Logicが数多くあります。ただし、これらは標準的な数学的手法よりも習得するのが難しく、ほとんどの生徒はそれらを扱うことができないため、ほとんど教えられません。
2つのパラダイムがどのように異なるかについての簡単な概要については、プログラミング言語の原則に関する私の講義ノートの最初のいくつかの配布資料を参照してください。
可変データ構造を使用するよりも、永続データ構造を正しく使用する方が簡単です。これが主な利点です。
もちろん、理論的に言えば、永続データ構造で行うことはすべて、可変データ構造でも行うことができ、その逆も同様です。多くの場合、永続的なデータ構造は、通常、その一部をコピーする必要があるため、余分なコストがかかります。これらの考慮事項により、スーパーコンピュータのメモリが携帯電話よりも少ない30年前には、永続的なデータ構造の魅力がはるかに低くなりました。しかし、今日では、ソフトウェアの生産における主なボトルネックは、開発時間と保守コストにあるようです。したがって、開発の効率のために、実行の効率をいくらか犠牲にすることをいとわない。
永続的なデータ構造を使用する方が簡単なのはなぜですか?人間は、プログラムの異なる部分間のエイリアシングやその他の種類の予期しない相互作用の追跡が本当に苦手だからです。彼らは自動的に、2つのものがとが呼ばれているので、それから共通点がないx
と考えるy
。すべての後、「朝の星」と「夜の星」は本当に同じものであると理解するのに努力が必要です。同様に、他のスレッドがデータ構造を処理しているため、またはデータ構造を変更するメソッドを呼び出したため、データ構造が変更される可能性があることを忘れがちです。これらの懸念の多くは、永続的なデータ構造。
永続的なデータ構造には、他の技術的な利点もあります。通常、それらを最適化する方が簡単です。たとえば、必要に応じてクラウド内の他のノードに永続的なデータ構造をいつでも自由にコピーできます。同期の心配はありません。
他の人の答えに加え、数学的アプローチを強化する関数型プログラミングは、リレーショナル代数やガロア接続との相乗効果も優れています。
これは、フォーマルメソッドの分野で非常に役立ちます。
例えば:
例
また、このアプローチにより、最も弱い事前条件と最も強力な事後条件の計算が可能になり、多くの状況で役立ちます。
データ構造が永続的でない場合はどうなるかを低レベルで理解したいのですが?
データ構造として、巨大な状態空間(状態が2450バイトの「メルセンヌツイスター」など)を持つ擬似乱数ジェネレーターを見てみましょう。乱数を2回以上使用したくないので、これを不変の永続データ構造として実装する理由はほとんどないようです。では、次のコードで何が間違っているのかを自問してみましょう。
mt_gen = CreateMersenneTwisterPRNGen(seed)
integral = MonteCarloIntegral_Bulk(mt_gen) + MonteCarloIntegral_Boundary(mt_gen)
ほとんどのプログラミング言語は順序を指定していないMonteCarloIntegral_Bulk
とMonteCarloIntegral_Boundary
評価されます。両方が可変mt_genへの参照を引数としてとる場合、この計算の結果はプラットフォームに依存する可能性があります。さらに悪いことに、異なる実行間で結果がまったく再現できないプラットフォームがあるかもしれません。
一つは、任意の実行のインタリーブようmt_genための効率的な可変データ構造を設計することができるMonteCarloIntegral_Bulk
とMonteCarloIntegral_Boundary
「正しい」結果を与えるが、異なる「正しい」結果に一般的なリードで異なるインターリーブします。この非再現性により、対応する機能が「不純」になり、他の問題も発生します。
固定の順次実行順序を強制することにより、非再現性を回避できます。ただし、その場合、コードは、mt_genへの単一の参照のみが常に利用できるように配置できます。型付き関数型プログラミング言語では、一意性型を使用してこの制約を強制することができます。これにより、純粋な関数型プログラミング言語のコンテキストでも安全で変更可能な更新が可能になります。これはすべて素晴らしくダンディに聞こえるかもしれませんが、少なくとも理論的にはモンテカルロシミュレーションは恥ずかしいほど平行です、そして私たちの「ソリューション」はこのプロパティを破壊しました。これは単なる理論上の問題ではなく、非常に現実的な実際的な問題です。ただし、疑似乱数ジェネレーター(生成される機能)と生成する乱数のシーケンスを変更する必要があります。これを自動的に行うプログラミング言語はありません。(もちろん、必要な機能を既に提供している別の擬似乱数ライブラリを使用できます。)
低レベルでは、実行順序がシーケンシャルで固定されていない場合、可変データ構造は容易に非再現性(したがって不純)につながります。これらの問題に対処するための典型的な必須の戦略は、可変のデータ構造が変更される実行順序が固定された順次フェーズと、すべての共有される可変データ構造が一定のままである任意の実行順序の並列フェーズを持つことです。
Andrej Bauerは、可変データ構造のエイリアスの問題を提起しました。興味深いことに、FortranやCなどのさまざまな命令型言語では、関数引数のエイリアシングの許容に関する前提が異なります。ほとんどのプログラマーは、言語にエイリアシングモデルがあることをまったく認識していません。
不変性と値のセマンティクスはわずかに過大評価される場合があります。さらに重要なのは、プログラミング言語の型システムと論理フレームワーク(抽象マシンモデル、エイリアシングモデル、同時実行モデル、メモリ管理モデルなど)が「効率的な」データで「安全に」動作するための十分なサポートを提供することです構造。「移動セマンティクス」をC ++ 11に導入することは、理論的な観点からは純度と「安全性」の点で大きな後退のように見えるかもしれませんが、実際には反対です。型システムと言語の論理フレームワークが拡張され、新しいセマンティクスに関連する危険の大きな部分が削除されました。(そして、粗いエッジが残っていても、これは「より良い」によってこれを改善できなかったという意味ではありません
Uday Reddyは、数学が変更可能なデータオブジェクトを扱うことは決してなく、関数型プログラムの型システムは不変のデータオブジェクトに対して十分に開発されているという問題を提起しました。これは、線形論理を動機付けようとするとき、変更可能なオブジェクトを扱うために数学は使用されないというジャンイブジラールの説明を思い出しました。
型システムと関数型プログラミング言語の論理フレームワークを拡張して、「効率的な」可変の非永続データ構造で「安全に」作業できるようにする方法を尋ねるかもしれません。ここでの問題の1つは、古典的な論理とブール代数が、可変データ構造を操作するための最良の論理フレームワークではないかもしれないことです。おそらく、そのタスクには線形論理と可換モノイドがより適しているでしょうか?多分私は、フィリップ・ワドラーが関数型プログラミング言語の型システムとして線形論理について言っていることを読むべきでしょうか?しかし、線形論理でこの問題を解決できなくても、関数型プログラミング言語の型システムと論理フレームワークを「安全」かつ「効率的」に拡張できないというわけではありません