94

私は48 時間以内に自分でスキームを書く(私は約 85 時間までです) に取り組んでおり、変数と代入の追加に関する部分に到達しました。この章には大きな概念上のジャンプがあります。最終的な解決策に直接ジャンプするのではなく、その間に適切なリファクタリングを行って 2 つのステップで実行されていればよかったのにと思います。とにかく…</p>

同じ目的を果たしているように見える多くの異なるクラスで迷子になりました: StateSTIORef、およびMVar. 最初の 3 つはテキストで言及されていますが、最後の 3 つは、最初の 3 つに関する多くの StackOverflow の質問に対する好まれる回答のようです。それらはすべて、連続する呼び出しの間に状態を保持しているようです。

これらはそれぞれどのようなもので、どのように互いに異なるのでしょうか?


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

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

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

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

4

3 に答える 3

121

State Monad : 可変状態のモデル

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

  • 得る
  • 置く

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

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

ST モナドと STRef

ST モナドは IO モナドの制限されたいとこです。

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

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

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

プライマリ API:

  • Control.Monad.ST
  • runST -- 新しいメモリ効果計算を開始します。
  • STRefs : (ローカル) ミュータブル セルへのポインタ
  • ST ベースの配列 (ベクトルなど) も一般的です。

IO モナドの危険性の少ない兄弟と考えてください。または、メモリの読み取りと書き込みのみが可能な IO。

IORef : IO 内の STRef

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

MVars : ロック付きの IORef

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

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

于 2011-04-04T23:44:52.797 に答える
37

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

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

Stateは、必ずしもIOを使用する必要はなく、可変状態を提供するモナドです。実際、これは純粋な計算に特に役立ちます。状態を使用するが使用しないアルゴリズムがある場合IOStateモナドは多くの場合、洗練されたソリューションです。

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

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

于 2011-04-04T23:55:57.160 に答える
17

他の人はコアなことをしましたが、直接の質問に答えるために:

これらすべてが線種をENV = IORef [(String, IORef LispVal)] 混乱させます。なぜ 2 番目の IORef なのか? type ENV = State [(String, LispVal)]代わりに行うと何が壊れますか?

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

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

于 2011-04-05T14:19:49.993 に答える