状態、ST、IORef、MVarの違い


91

私は48時間でスキーマの作成に取り組んでいます(最大で約85 時間です)、変数と割り当ての追加に関する部分にたどり着きました。この章には大きな概念的なジャンプがあります。最終的なソリューションに直接ジャンプするのではなく、2つのステップで適切なリファクタリングを行って実行したいと思います。とにかく…

:私は、同じ目的を果たすように見える別のクラスの数を失ってもらいましたStateSTIORef、とMVar。最初の3つはテキストで言及されていますが、最後の3つは、最初の3つに関する多くのStackOverflowの質問に対する好ましい回答のようです。それらはすべて、連続した呼び出しの間に状態を運ぶようです。

これらはそれぞれ何であり、どのように互いに異なるのですか?


特に、次の文は意味がありません。

代わりに、状態スレッドと呼ばれる機能を使用して、Haskellに集約状態を管理させます。これにより、関数を使用して変数を取得または設定し、他のプログラミング言語と同様に可変変数を処理できます。

そして

IORefモジュールを使用すると、IOモナド内でステートフル変数を使用できます。

これらすべてにより、行がtype ENV = IORef [(String, IORef LispVal)]混乱します-なぜ2番目IORefですか?type ENV = State [(String, LispVal)]代わりに書くと何が壊れますか?

回答:


119

状態モナド:可変状態のモデル

Stateモナドは、単純なAPIを備えた、状態のあるプログラムのための純粋に機能的な環境です。

  • 取得する
  • 置く

mtlパッケージのドキュメント。

Stateモナドは、単一の制御スレッドで状態を必要とするときに一般的に使用されます。実際の実装では、変更可能な状態を使用しません。代わりに、プログラムは状態値によってパラメーター化されます(つまり、状態はすべての計算の追加パラメーターです)。状態は単一のスレッドでのみ変化するように見えます(スレッド間で共有することはできません)。

STモナドとSTRefs

STモナドはIOモナドの制限された従兄弟です。

マシン上の実際の可変メモリとして実装される、任意の可変状態を許可します。副作用のないプログラムではAPIが安全になります。ランク2の型パラメーターは、変更可能な状態に依存する値がローカルスコープをエスケープするのを防ぐためです。

したがって、それ以外の場合は純粋なプログラムで制御された可変性を可能にします。

一般に、変更された後にフリーズされる可変配列やその他のデータ構造に使用されます。変更可能な状態は「ハードウェアアクセラレーション」されているため、これも非常に効率的です。

主要なAPI:

  • Control.Monad.ST
  • runST-新しいメモリ効果の計算を開始します。
  • そしてSTRefs:(ローカル)変更可能な細胞へのポインタ。
  • STベースの配列(ベクターなど)も一般的です。

それをIOモナドのそれほど危険でない兄弟と考えてください。または、メモリへの読み取りと書き込みのみが可能なIO。

IORef:IOのSTRef

これらは、IOモナドのSTRef(上記を参照)です。地域性に関するSTRefと同じ安全性の保証はありません。

MVars:ロック付きのIORef

STRefやIORefと同様ですが、複数のスレッドからの安全な同時アクセスのためにロックがアタッチされています。IORefとSTRefは、マルチスレッド設定でのみ使用できますatomicModifyIORef(比較およびスワップのアトミック操作)。MVarは、変更可能な状態を安全に共有するためのより一般的なメカニズムです。

通常、Haskellでは、STRefまたはIORefではなく、MVarまたはTVar(STMベースの可変セル)を使用します。


3
MVarのMとTVarのTは何ですか?「Mutable」、「Transactional」だと思います。STがどのようにState Threadを意味するのか興味深い。
CMCDragonkai 2015

10
なぜそれをMVar優先するべきだと言うのSTRefですか?STRef1つのスレッドのみが変更できることを保証します(他のタイプのIOが発生しないことを保証します)-変更可能な状態への同時アクセスが必要ない場合は確かにそれが優れていますか?
ベンジャミンホジソン

@CMCDragonkai私はいつもMがミューテックスを表すと思っていましたが、どこにも記載されていません。
Andrew Thaddeus Martin

37

では、始めましょうIORefIORefIOモナドで変更可能な値を提供します。これは一部のデータへの単なる参照であり、他の参照と同様に、参照するデータを変更できる関数があります。Haskellでは、これらの関数はすべてで動作しIOます。データベース、ファイル、その他の外部データストアのようなものと考えることができます。データを取得および設定できますが、そのためにはIOを実行する必要があります。IOがまったく必要なのは、Haskellが純粋だからです。コンパイラーは、参照が常にどのデータを指しているかを知る方法を必要とします(sigfpeの「あなたはモナドを発明したかもしれない」ブログ記事を読んでください)。

MVarsは、2つの非常に重要な違いを除いて、基本的にIORefと同じです。 MVarは並行処理プリミティブであるため、複数のスレッドからのアクセス用に設計されています。2つ目の違いは、MVarは完全または空のボックスであることです。したがって、IORef Int常にがInt(または一番下)にあるMVar Int場合、はを持つIntか、または空になることがあります。スレッドが空のから値を読み取ろうとすると、(別のスレッドによって)がいっぱいにMVarなるまでブロックMVarされます。基本的に、MVar aIORef (Maybe a)、並行処理に役立つ追加のセマンティクスを持つと同等です。

State可変状態を提供するモナドであり、必ずしもIOを必要としません。実際、純粋な計算に特に役立ちます。状態ではなくを使用するアルゴリズムがある場合IOStateモナドはエレガントなソリューションであることがよくあります。

Stateのモナド変換バージョンもありStateTます。これは、プログラム構成データ、またはアプリケーションの「ゲーム世界の状態」タイプの状態を保持するために頻繁に使用されます。

ST少し異なります。の主なデータ構造はSTですSTRef。これはに似てIORefいますが、モナドが異なります。STモナドは、可変データがモナドを免れることはできないことを保証するために、型システムの策略(「状態のスレッド」ドキュメントが言及)を使用しています。つまり、ST計算を実行すると、純粋な結果が得られます。STが興味深いのは、STがIOのようなプリミティブモナドであるため、計算でバイト配列とポインタに対して低レベルの操作を実行できるためです。これは、ST可変データに対して低レベルの操作を使用しながら純粋なインターフェースを提供できることを意味します。つまり、非常に高速です。プログラムの観点からは、ST計算はスレッドローカルストレージを備えた別のスレッドで実行されているかのようです。


17

他の人たちは核心的なことをしましたが、直接の質問に答えるために:

これにより、線種がENV = IORef [(String, IORef LispVal)] わかりにくくなります。なぜ2番目のIORefなのですか?type ENV = State [(String, LispVal)]代わりに何をしたら壊れますか?

Lispは、状態と字句スコープが変更可能な関数型言語です。可変変数を閉じたとしましょう。これで、この変数への参照が他の関数の内部にぶら下がってい(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)ます。たとえば、(haskellスタイルの疑似コードで)これで2つの関数があります-1つはxを出力し、もう1つはその値を設定します。を評価するときprintIt、定義された初期環境でx の名前を検索しprintItたいが、呼び出された環境で名前がバインドされているを検索したい(後で何度も呼び出された可能性がある) )。printItsetIt

2つのIORefを使用してこれを行う方法はいくつかありますが、提案した後者のタイプよりも確かに多くのタイプが必要であり、レキシカルスコープで名前がバインドされている値を変更することはできません。グーグルは、多くの興味深い先史時代の「funargs問題」です。

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