私は 48時間であなた自身のスキームを書く (私は約85時間です)を通して作業しており、 変数と割り当ての追加 についての部分に達しました。この章には大きな概念的なジャンプがあります。最終的なソリューションに直接ジャンプするのではなく、2つのステップで適切なリファクタリングを行って実行したいと思います。とにかく…
State
、ST
、IORef
、およびMVar
のように、同じ目的を果たしていると思われるいくつかの異なるクラスで迷子になっています。最初の3つはテキストで説明されていますが、最後の3つは、最初の3つに関する多くのStackOverflowの質問に対する好ましい回答のようです。それらはすべて、連続した呼び出しの間に状態を運ぶようです。
これらはそれぞれ何であり、どのように互いに異なるのですか?
特に、次の文は意味がありません。
代わりに、state threadsと呼ばれる機能を使用して、Haskellに集約状態を管理させます。これにより、関数を使用して変数を取得または設定し、他のプログラミング言語と同様に可変変数を処理できます。
そして
IORefモジュールを使用すると、ステートフル変数IOモナド内で使用できます。
これらすべてがtype ENV = IORef [(String, IORef LispVal)]
の行を混乱させています-なぜ2番目のIORef
ですか?代わりにtype ENV = State [(String, LispVal)]
と書くと何が壊れますか?
State Monad:可変状態のモデル
Stateモナドは、単純なAPIを備えた、状態のあるプログラムのための純粋に機能的な環境です。
mtlパッケージ のドキュメント。
Stateモナドは、単一の制御スレッドで状態を必要とするときに一般的に使用されます。実際の実装では、変更可能な状態を使用しません。代わりに、プログラムは状態値によってパラメーター化されます(つまり、状態はすべての計算の追加パラメーターです)。状態は単一のスレッドでのみ変化しているように見えます(スレッド間で共有することはできません)。
STモナドとSTRefs
STモナドは、IOモナドの制限されたいとこです。
これは、任意の可変状態を許可し、マシン上の実際の可変メモリとして実装されます。ランク2タイプパラメータは、変更可能な状態に依存する値がローカルスコープをエスケープするのを防ぐため、APIは副作用のないプログラムで安全になります。
したがって、それ以外の場合は純粋なプログラムで制御された可変性を可能にします。
通常、変更可能な配列やその他のデータ構造に使用され、変更されてから凍結されます。変更可能な状態は「ハードウェアアクセラレーション」されているため、これも非常に効率的です。
主要なAPI:
IOモナドのそれほど危険でない兄弟と考えてください。または、メモリへの読み取りと書き込みしかできないIOです。
IORef:IOのSTRefs
これらは、IOモナドのSTRef(上記を参照)です。局所性に関するSTRefと同じ安全性保証はありません。
MVars:ロック付きのIORef
複数のスレッドからの安全なconcurrentアクセスのために、STRefまたはIORefと同様ですが、ロックがアタッチされています。 IORefとSTRefは、atomicModifyIORef
(比較およびスワップのアトミック操作)を使用する場合に、マルチスレッド設定でのみ安全です。 MVarは、変更可能な状態を安全に共有するためのより一般的なメカニズムです。
通常、Haskellでは、STRefまたはIORefではなく、MVarまたはTVar(STMベースの可変セル)を使用します。
では、IORef
から始めましょう。 IORef
は、IOモナドで変更可能な値を提供します。これは、いくつかのデータへの単なる参照であり、他の参照と同様に、データを変更できる関数がありますHaskellでは、これらの関数はすべてIO
で動作します。データベース、ファイル、またはその他の外部データストアのように考えることができます-データを取得および設定できますが、そのためにはIOを通過します。IOが必要な理由は、Haskellがpureであるためです。コンパイラは参照がどの時点でどのデータを指しているかを知ってください( sigfpeの「あなたはモナドを発明したかもしれません」 ブログ投稿を読んでください).
MVar
sは、2つの非常に重要な違いを除いて、基本的にIORefと同じです。 MVar
は並行処理プリミティブであるため、複数のスレッドからのアクセス用に設計されています。 2番目の違いは、MVar
がボックスであり、いっぱいになることも空になることもあります。したがって、_IORef Int
_には常にInt
(または一番下)があるのに対し、_MVar Int
_にはInt
が含まれるか、空である可能性があります。スレッドが空のMVar
から値を読み取ろうとすると、MVar
が(別のスレッドによって)満たされるまでブロックされます。基本的に_MVar a
_は、同時実行に役立つ追加のセマンティクスを持つIORef (Maybe a)
と同等です。
State
は変更可能な状態を提供するモナドであり、必ずしもIOではありません。実際、純粋な計算に特に役立ちます。 IO
ではなくstateを使用するアルゴリズムがある場合、State
モナドは多くの場合エレガントなソリューションです。
Stateのモナド変換バージョンStateT
もあります。これは、プログラム構成データ、またはアプリケーションの「ゲーム世界の状態」タイプの状態を保持するために頻繁に使用されます。
ST
は少し異なります。 ST
の主なデータ構造はSTRef
で、IORef
と似ていますが、モナドが異なります。 ST
モナドは、型システムのトリック(ドキュメントが言及する「状態スレッド」)を使用して、可変データがモナドをエスケープできないようにします。つまり、ST計算を実行すると、純粋な結果が得られます。 STが興味深いのは、STがIOのようなプリミティブモナドであるため、計算でバイト配列とポインタに対して低レベルの操作を実行できるためです。つまり、ST
は、可変データに対して低レベルの演算を使用しながら純粋なインターフェイスを提供できるため、非常に高速です。プログラムの観点から見ると、ST
計算は、スレッドローカルストレージを備えた別のスレッドで実行されているかのようです。
他の人たちは核心的なことをしましたが、直接の質問に答えるために:
これらすべてにより、行タイプ
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つはその値を設定します。 printIt
を評価するとき、printIt
が定義された初期環境でxのnameを検索する必要がありますが、-値printIt
が存在する環境で名前がバインドされている呼び出された(setIt
が何度も呼び出された後)。
2つのIORefを使用してこれを行う方法はいくつかありますが、提案した後者のタイプよりも確かに多くのタイプが必要であり、レキシカルスコープの方法で名前がバインドされている値を変更することはできません。グーグルは、多くの興味深い先史時代の「funargs問題」です。