2012-02-01 6 views
6

私は4ビットのマイクロプロセッサをエミュレートしています。私は、レジスタ、メモリ、実行中の出力(フェッチ実行サイクルカウンタを持つためのボーナスポイント)を追跡する必要があります。私はモナドなしでこれを行うことができましたが、それはあまりにも多くのものを明示的に一気に渡しています。また、関数の定義が乱雑で長くて読みにくいです。haskellの異なるレベルの相互作用レベル

私はモナドでこれをしようとしましたが、それだけでは合わないのです。私は別々の州のすべてのコンポーネントを単一のタイプとして扱うことを試みましたが、それは私に価値をどうにかするのかの問題を残しました。

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

これは意味をなさない唯一のタイプです。しかし、その時点でなぜ気になるのでしょうか?私は私の複合型の外に列を引っ張り、私はRUNNING出力を必要としているという事実を除いて、とてもうまくいった価値

State Program' String 

としてそれを処理することにより、それを破壊しようとしました。私が何をしたとしても、私は文字列と状態の両方を同時に保持することができませんでした。

私はモナド変圧器に取り組もうとしています。私はすべての異なるレベルの国家を分離しなければならないようです。しかし、私の頭は急激に爆発しています。

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つの別個の層を見ることができますが、お互いに密接に依存しています(メモリとレジスタの両方がメモリとレジスタの両方の状態に依存します)。私はそれらを単一の状態としてまとめるか、それらを分けて積み重ねるべきですか?どちらのアプローチが最も読みやすいコードを生成するでしょうか?
+1

「実行出力」は、おそらくWriterモナド(http://monads.haskell.cz/html/writermonad.htmlを参照)を使用して最もよく表現されますか? –

答えて

9

複数の状態のモナドを重ね合わせることは悪い考えです。それぞれの状態を取得するには、liftの束を作成しなければなりません。ヤク!実際、mtlライブラリは一般的にはまれな例外を除いてスタック内の各「種類」のモナド変圧器を1つ使用して使用するように設計されています。

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

状態の各部分に便利にアクセスできる限り、あなたが探しているのはレンズです。 this Stack Overflow answerは優れた紹介です。彼らは、あなたの状態の独立した部分に簡単にアクセスして変更することができます。たとえば、data-lensの実装では、regA += 1のようなものを簡単に作成して、regA、またはstack %= drop 2を増やしてスタックの最初の2つの要素を削除することができます。

確かに、それは本質的グローバル変数のセットの不可欠変異にコードを回していますが、それはあなたがエミュレートしているCPUがに基づいて正確にパラダイムだから、これは、実際に有利である。そして、data-lens-templateパッケージとこれらのレンズをレコード定義から1行で派生させることができます。

+0

それはすごいですね。テンプレートのhaskellを学ぶ時間。 regA + = 1のような機能定義バッキングを見つけるのはまだ簡単ですか?それは素晴らしく読みやすいし、(非常に不可欠な)意図を最も明確に表現しているので、機能コードのようには見えません。 – TheIronKnuckle

+1

data-lens-templateを使用するにはTemplate Haskellを習得する必要はありません。あなたのファイルの先頭に '{ - #LANGUAGE TemplateHaskell# - }'と 'Program'の定義の下の' makeLenses ["'' Program]'を貼り付けてください。限り、 '(+ =)'の定義、確かに;彼らは 'StateT'のコアレンズAPIの単純なラッパーです。 [ソース](http://hackage.haskell.org/packages/archive/data-lens/2.0.2/doc/html/src/Data-Lens-Strict.html)は、Hackageのドキュメントからリンクされています。 – ehird

+2

少々不安定な層状の状態のモナドは悪い考えです。これらは、状態のパーティション化を許可するので、一部のクライアントが読み書きアクセスではなく状態の特定の層への読み取りアクセスしか持たないように制限できます。これは「セキュリティ意識の高い」コードで使用されます。そうでなければ良い答え。 –

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タイプはマシンの状態を表します。前の操作の出力をスレッド化しています。

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

関連する問題