質問

私は最近 質問した 関数型プログラミングについて質問し、さらに質問を促す (良い!) 回答を受け取りました (学習の場合にはそうであるように思われます)。以下にいくつかの例を示します。

  1. 1 つの回答では、不変のデータ構造の利点について言及しています。各スレッドは独自のコピーを持つことができます。さて、私には、これは (例えて言うと) バージョン管理システムのように聞こえます。誰かがチェックアウトしたコードを他の人が変更できないようにロックする代わりに、誰もが自分のコピーをチェックアウトできます。いいですね。ただし、VCS には、2 人が同じ内容を変更した場合に備えて、変更を「マージ」するという概念があります。この問題はマルチスレッドのシナリオで確実に発生する可能性があるようです...では、スレッドが最新のデータを参照することが重要な場合、「マージ」はどのように行われるのでしょうか?

  2. この答えは オブジェクトのループ内で操作が実行される場合と、古いオブジェクトを更新する代わりに毎回新しいオブジェクトを使用する方法について説明しました。ただし、次のようにしましょう。 bankAccount 非ループ シナリオ (GUI バンキング システムなど) で更新されます。オペレーターが「金利の変更」ボタンをクリックすると、(たとえば C# の場合) 次のようなことを行うイベントが起動されます。 bankAccount.InterestRate = newRateFromUser. 。ここでは内容が濃くなっているように感じますが、私の例が意味をなすことを願っています。オブジェクトを更新する何らかの方法が必要ですよね?他にもいくつかのことが新しいデータに依存する可能性があります。

とにかく、パラダイムシフトについて理解するのを手伝っていただければ幸いです。コーディングに対する単純な手続き型命令型アプローチを背景にして OOP を学習したとき、私の脳が同様の「愚かな段階」を経験したことを覚えています。

役に立ちましたか?

解決

パート 1 の答え:不変オブジェクト自体は、2 つのスレッドの更新結果を結合できるようにする「マージ」などをサポートしません。そのためには 2 つの主要な戦略があります。悲観的なものと楽観的なもの。悲観的であれば、2 つのスレッドが同じデータを同時に更新する可能性が非常に高いと想定します。そこで、最初のスレッドが終了を通知するまで 2 番目のスレッドがフリーズするように、ロックを採用します。このようなことはめったに起こらないと楽観的に考える場合は、両方のスレッドがデータの独自の論理コピーを処理できるようにします。最初に終了したものが新しいバージョンを提供し、もう一方は最初からやり直す必要があります。ただし、最初のスレッドの変更結果から開始するだけです。ただし、この高価な再起動はたまにしか発生しないため、ロックがないため全体的にパフォーマンスが向上します (ただし、これは、衝突がめったに発生しないという楽観的な見方ができた場合にのみ当てはまります)。

パート2:純粋な関数型ステートレス言語では、実際にはこの問題が解決されません。純粋な Haskell プログラムであっても、状態を関連付けることができます。違いは、ステートフル コードの戻り値の型が異なることです。状態を操作する関数は、その状態を表すオブジェクトに対して動作する一連の操作として表現されます。不合理な例として、コンピュータのファイル システムを考えてみましょう。プログラムがファイルの内容を (たとえ 1 バイトでも) 変更するたびに、ファイル システム全体の新しい「バージョン」が作成されます。さらに言えば、宇宙全体の新しいバージョンです。ただし、ここではファイル システムに焦点を当てましょう。ファイル システムを検査するプログラムの他の部分は、その変更されたバイトの影響を受ける可能性があります。したがって、Haskell は、ファイル システム上で動作する関数は、ファイル システムのバージョンを表すオブジェクトを効果的に受け渡す必要があると述べています。次に、これを手動で処理するのは面倒なので、要件を裏返しにして、関数が IO を実行できるようにしたい場合は、ある種のコンテナ オブジェクトを返さなければならないとします。コンテナー内には、関数が返したい値が入っています。しかし、コンテナーは、関数にも副作用がある、または副作用が発生する可能性があるという証拠として機能します。これは、Haskell の型システムが副作用のある関数と「純粋な」関数を区別できることを意味します。したがって、コードのステートフル性を実際に排除することなく、封じ込めて管理するのに役立ちます。

他のヒント

(不変オブジェクトである).NETでStringクラスについて考えてみよう。あなたは、文字列でメソッドを呼び出す場合は、新しいコピーを入手ます:

String s1 = "there";
String s2 = s1.Insert(0, "hello ");

Console.Writeline("string 1: " + s1);
Console.Writeline("string 2: " + s2);

この意志出力ます:

の文字列1:そこに

の文字列2:こんにちは

基本的に同じメソッドシグネチャを有するのStringBuilder、この現象の比較

StringBuilder sb  = new StringBuilder("there");
StringBuilder sb2 = sb.Insert(0, "hi ");

Console.WriteLine("sb 1: " + sb.ToString());
Console.WriteLine("sb 2: " + sb2.ToString());

のStringBuilderが可変であるため、両方の変数が同じオブジェクトを指します。出力は次のようになります。

のSB 1:こんにちは

のSB 2:こんにちは

だから、あなたは絶対にあなたがそれを作成した後に文字列を変更することはできません。 s1は常に時間の終わり(またはそのゴミが収集されるまで)まで、「そこ」になります。あなたは、常にそれぞれの文字をステップ実行し、それは常に「そこ」に印刷されますことを知って、その値を印刷することができますので、それはスレッドで重要です。それが作成された後、あなたがStringBuilderのを印刷し始めた場合は、「そことget'thの最初の2つの文字を印刷することがあります。今、別のスレッドが広告を挿入「ハイ」に沿って来て想像してみてください。値は今違います!あなたは3番目の文字を印刷するとき、それは「ハイ」からスペースです。だから、印刷: 'そこ番目の'

#2に関して...

  

いくつかの他のものは、に依存してもよいです   新しいデータます。

これは純粋主義者は「効果」と呼んでいます。同じ変更可能なオブジェクトへの複数のオブジェクト参照の概念は、可変状態の本質と問題の核心です。 OOPでは、あなたはタイプのBankAccountのオブジェクト「A」を有することができ、あなたは別のの回のでその他もろもろa.Balanceを読むかどうかは別の値を表示される場合があります。これとは対照的に、純粋なFPに、「」銀行口座のを入力した場合、それは不変だと時間によらず同じ値を持っています。

が銀行口座のは、おそらく我々はその状態が時間と共に変化んモデル化するオブジェクトであるので、我々は、FPタイプで、その情報を符号化するであろう。だから、「」タイプ「IOのBankAccount」、または本質的に「」実際に「世界の前の状態」を入力として受け取る関数も作ることに帰着するいくつかの他のモナドのタイプを持っているかもしれない(または銀行の金利の前の状態、または何でも)、および世界の新しい状態を返します。金利を更新する効果を表しタイプ(例えば、別のIO操作)と、他の操作となり、ひいては新しい「世界」を返す、と金利(世界の状態)に依存しうるすべてが持つデータになりますそれを知っているタイプは、入力としてその世界を取る必要があります。

その結果、「a.Balance」やその他もろもろを呼び出すための唯一の可能な方法は、静的な型のおかげで、いくつかの「今まで私たちを得た世界史」は適切に配管されていることを強制し、コードを使用することですコールのポイント、そしてどんな世界史が入力されているが、我々はa.Balanceから買ってあげる結果と内容が変わります。

状態モナドの上に読むことは感覚を得るために有用である可能性がありますあなたは純粋に「共有可変状態」をモデル化する方法のます。

  1. 不変のデータ構造は VCS とは異なります。不変データ構造は読み取り専用ファイルと考えてください。読み取り専用の場合、いつでも誰がファイルのどの部分を読んでいるかは問題ではなく、全員が正しい情報を読み取ることになります。

  2. その答えが話しているのは http://en.wikipedia.org/wiki/Monad_(関数型プログラミング)

MVCCするのマルチバージョン同時実行制御の)

あなたが参照している問題の解決策は、href="http://clojure.blip.tv/" rel="nofollow noreferrer">ビデオプレゼンテーションを彼の

を一言で言えば:代わりにクライアントに直接参照することにより、データを渡すので、あなたは間接に1つのより多くのレベルを追加し、データへの参照への参照を渡します。 (のまあ、実際には、間接の少なくとももう1つのレベルを持ちたい。しかし、ここでは、データ構造は、「配列」のような、非常に単純であると仮定しましょう。のでしょう)
データは不変なので、データが変更されなければならないたびに、あなたが変更された部分のコピーを作成します(のあなたは別の配列を作成する必要があり、配列の場合は!の)プラスあなたが別の参照を作成し、すべてのデータを「変更」。
だから、配列の第1版を使用するすべてのクライアントのために、彼らは最初のバージョンへの参照を使用します。第二版にアクセスしようとするすべてのクライアントは、第二の基準を使用しています。
あなたがデータを分割することはできません、あなたはすべてのものをコピーすることを余儀なくされているので、「アレイ」のデータ構造は、この方法のために非常に興味深いものではありません。しかし、木のような、より高度なデータ構造のために、データ構造のいくつかの部分は、「共有」することができますので、あなたはすべてのものを毎回コピーすることを余儀なくされていません。

は、詳細については、この論文を見てください。」純粋に機能データ構造 "のクリス・オカサキによってします。

「不変」はまさにそれを意味します。それは変わりません。

関数プログラムが更新を行う方法は、新しいものの周りに渡すことです。既存の値は変更されません:あなただけの新しい価値を構築し、代わりにそれを渡します。古いものと非常に頻繁に新しい価値を共有状態。技術の良い例は、コンス・セルで構成リストであり、ジッパーに。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top