2012-02-01 2 views
6

Я эмулирую 4-битный микропроцессор. Мне нужно следить за регистрами, памятью и текущим выходом (бонусные баллы за счет счетчика цикла выборки). Мне удалось сделать это без монадов, но он чувствует себя грязным, проходя мимо этого многого материала сразу же. Также определение функции грязно, долго и трудно читать.Различные, взаимодействующие уровни состояния в haskell

Я пытался сделать это с монадами, и он просто не подходит друг другу. Я пробовал обрабатывать все отдельные компоненты состояния как один тип, но это оставило мне проблему того, что делать.

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. Am Я на правильном пути?
  2. Видя, как я вытаскиваю трансформаторы монады сейчас, можно ли прекратить обрабатывать «бегущий выход» как состояние и просто поднести его к монаде IO? Это было бы потрясающе, вместо того, чтобы держаться за него, я мог просто распечатать его.
  3. Сколько слоев следует отделить в? Я вижу два разных слоя, но они зависят друг от друга близко (как память, так и регистры зависят от состояния как памяти, так и регистров). Должен ли я держать их вместе как единое государство или разделять их и складывать? Какой подход даст самый читаемый код?
+1

Возможно, «работающий выход» может быть лучше всего представлен с использованием монады Writer (см. Http://monads.haskell.cz/html/writermonad.html)? –

ответ

9

Многоуровневые монады штатов друг на друга - это плохая идея: вам нужно собрать кучу lift s, чтобы получить каждую часть состояния, определенную только количеством слоев вниз по стеку. Тьфу! В самом деле, библиотека mtl в целом предназначена для использования, за редким исключением, с одним монадным трансформатором каждого «вида» в стеке.

Вместо этого я предлагаю StateT Program IO(). Интерфейс к состоянию тот же, и вы можете, как вы сказали, делать вывод в IO, просто используя liftIO. Конечно, тип значения - (), но что не так? Нет никакого релевантного значения. может вернуться с эмулятора верхнего уровня. И, конечно же, вы, вероятно, будете иметь меньшие, многоразовые компоненты как часть вашего эмулятора, и у них будут соответствующие типы результатов. (Действительно, get является одним из таких компонентов.) Нет ничего плохого в том, что у него нет значимого возвращаемого значения на верхнем уровне.

Насколько удобно доступ к каждой части состояния, то, что вы ищете, - это линзы; this Stack Overflow answer - отличное введение. Они позволяют вам просто и легко получить доступ и изменить независимые части вашего состояния. Например, с реализацией data-lens вы можете легко написать что-то вроде regA += 1 для увеличения regA или stack %= drop 2 для удаления первых двух элементов стека.

Конечно, это по сути превращая ваш код в повелительную мутацию набора глобальных переменных, но это на самом деле является преимущество, так как это именно парадигмы процессора вы эмуляция базируется. И с data-lens-template пакетом , вы можете получить эти линзы из определения записи в одной строке.

+0

Это чертовски классно. Время изучать шаблон haskell. Легко ли найти функциональное определение, поддерживающее такие вещи, как regA + = 1? Причина в том, что это хорошо и понятно и наиболее четко выражает (очень императивное) намерение, оно не похоже на функциональный код. – TheIronKnuckle

+1

Вам не нужно изучать Template Haskell для использования шаблона данных-объективов; просто вставьте '{- # LANGUAGE TemplateHaskell # -}' вверху вашего файла и 'makeLenses ['' Program]' где-то ниже определения 'Program'. Что касается определения '(+ =)', обязательно; это просто простые обертки API-интерфейсов для ядра для StateT; [источник] (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). Они позволяют использовать изменяемые ссылки и массивы внутри функции, гарантируя чистоту снаружи.

Смежные вопросы