6

私は4ビットのマイクロプロセッサをエミュレートしています。レジスタ、メモリ、および実行中の出力を追跡する必要があります (フェッチ実行サイクル カウンターもあるというボーナス ポイント)。私はモナドなしでこれを行うことができましたが、一度に多くのものを明示的に渡すのは面倒です。また、関数定義はごちゃごちゃしていて、長くて読みにくいです。

私はモナドでこれをやろうとしましたが、うまくいきません。個別の状態コンポーネントをすべて 1 つの型として扱ってみましたが、値を何にするかという問題が残りました。

State Program () -- Represents the state of the processor after a single iteration of the fetch execute cycle

意味のある唯一のタイプでした。しかし、その時点で、なぜ気にする必要があるのでしょうか。複合型から文字列を取り出して値として扱うことで、分割しようとしました

State Program' String

RUNNING 出力が必要だったという事実を除いて、これはうまく機能しました。私が何をしても、文字列と状態の両方を同時に保持することはできませんでした。

今、私はモナドトランスフォーマーに取り組もうとしています。さまざまなレベルの状態をすべて分離する必要があるようです。しかし、私の頭は急速に爆発しています。

StateT Registers (StateT Memory (State Output)) a = 
StateT (registers -> (StateT Memory (State Output)) (a,registers))

StateT Registers (StateT Memory (State Output)) a = 
StateT (registers -> (Memory -> (Output -> (((a,Registers),Memory),Output))))

FEcycle カウンターもまだ入れていません!

質問:

  1. 私は正しい軌道に乗っていますか?
  2. モナドトランスフォーマーを引き抜いているのを見て、「実行中の出力」を状態として扱うのをやめて、それを IO モナドにパームオフすることは可能ですか? それを保持する代わりに、印刷するだけでよいのです。
  3. 状態を何層に分ける必要がありますか? 2 つの異なる層が見えますが、それらは互いに密接に依存しています (メモリとレジスタの両方が、メモリとレジスタの両方の状態に依存しています)。ひとつにまとめておくべきか、バラバラにして積み上げるべきか。最も読みやすいコードを生成するアプローチはどれですか?
4

2 に答える 2

9

複数の状態モナドを互いに重ねることは悪い考えです。lift各状態を取得するには、スタックの下の層の数によってのみ識別される一連のsを作成する必要があります。うん!実際、mtlライブラリは一般に、まれな例外を除いて、スタック内の各「種類」の1つのモナド変換子で使用するように設計されています。

代わりに、私は提案しStateT Program IO ()ます。状態へのインターフェースは同じであり、あなたが言ったように、IOを使用するだけで出力を行うことができますliftIO。確かに、値型はです()が、何が問題になっていますか?トップレベルエミュレータから返すことができる関連する値はありません。そしてもちろん、エミュレーターの一部として、より小さく、再利用可能なコンポーネントを使用する可能性が高く、それらには関連する結果タイプがあります。(確かに、getそのようなコンポーネントの1つです。)トップレベルに意味のある戻り値がないことには何の問題もありません。

州の各部分に便利にアクセスできる限り、あなたが探しているのはレンズです。このStackOverflowの回答は、優れた入門書です。州の独立した部分に簡単かつ簡単にアクセスして変更できます。たとえば、データレンズregA += 1の実装では、インクリメントregAしたりstack %= drop 2、スタックの最初の2つの要素を削除したりするようなものを簡単に記述できます。

確かに、それは本質的にコードをグローバル変数のセットの命令型ミューテーションに変えますが、これは実際には利点です。これは、エミュレートしているCPUがまさに基づいているパラダイムだからです。そして、data-lens-templateパッケージを使用すると、これらのレンズは、1行のレコード定義から導き出すことができます。

于 2012-02-01T09:30:13.837 に答える
2

これを行う簡単な方法は、レジスタとメモリを表すデータ型を作成することです。

data Register = ...
data Memory = ...
data Machine = Machine [Register] Memory

次に、レジスタ/メモリを更新する関数をいくつか用意します。このタイプを状態に使用し、タイプの出力を使用します。

type Simulation = State Machine Output

これで、各操作は次の形式になります。

operation previous = do machine <- get
                        (result, newMachine) <- operate on machine
                        put newMachine
                        return result

これpreviousは、マシンの以前の出力です。それを結果に組み込むこともできます。

したがって、Machine型はマシンの状態を表します。以前の操作の出力をそれを通してスレッド化しています。

より洗練された方法は、状態スレッド(Control.Monad.ST) を使用することです。これらを使用すると、関数内で変更可能な参照と配列を使用でき、外部では純粋性が保証されます。

于 2012-02-01T09:25:48.270 に答える