Different, interacting status levels in haskell

I am emulating a 4 bit microprocessor. I need to keep track of registers, memory and current output (bonus points due to the sampling cycle counter). I managed to do this without the monads, but it feels dirty, walking past this lots of stuff right away. Also the definition of a function is dirty, long and hard to read.

I tried to do this with monads, and it just doesn't fit together. I tried to treat all the individual state components as one type, but that left me with a problem of what to do.

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

There was the only type that made any sense. But at this point, why even bother? I tried to break it by pulling a string from my composite type and treating it as a value

 State Program' String 

which worked great except that I need a RUNNING output. No matter what I did, I could not hold onto the line and the state at the same time.

Now I'm trying to grab monad transformers. It seems I should separate all the different levels of state. But the head explodes quickly.

 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)))) 

I have not set FEcycle counter yet!

Questions:

  • Am I on the right track?
  • Having seen how I am taking out the monad transformers now, is it possible to stop treating the “running output” as a state and simply postpone it to the IO monad? That would be awesome, instead of holding onto it, I could just print it.
  • How many layers should I separate? I see two different layers, but they depend on each other closely (both memory and registers depend on the state of both memory and registers). Should I keep them together as a single state or separate them and add? Which approach will give the most readable code?
+6
source share
2 answers

Multilayer multilayer monads on top of each other is a bad idea: you will need to collect a bunch of lift to get each part of the state, determined only by the number of layers down the stack. Ugh! In fact, the mtl library as a whole is intended to be used, with rare exceptions, with one monadic transformer of each "view" on the stack.

Instead, I would suggest StateT Program IO () . The interface to the state is the same, and you can, as you said, make a conclusion in IO simply using liftIO . Of course, the value type is () , but what is wrong with that? There is no important value that you can return from a top-level emulator. And, of course, you are likely to have smaller, reusable components as part of your emulator, and they will have corresponding types of results. (Indeed, get is one of these components.) There is nothing wrong with having no meaningful return value at the top level.

How convenient is access to every part of the state, what you are looking for are lenses; this answer is a great introduction. They allow you to access and change the independent parts of your state simply and easily. For example, using data-lens, you can easily write something like regA += 1 to increase regA or stack %= drop 2 to delete the first two elements of the stack.

Of course, this turns your code into an imperative mutation of a set of global variables, but this is actually an advantage, since it is in this paradigm that the processor on which you emulate is based. And with data-lens-template , you can get these lenses from the record definition in one line.

+9
source

An easy way to do this is to create a data type that represents registers and memory:

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

Then you have some functions that update the registers / memory. Now use this type for your state and output for your type:

 type Simulation = State Machine Output 

Now each operation can be in the form of:

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

Here previous is the previous output of the machine. You can also include it in the result.

Thus, the Machine type represents the state of the machine; you permeate the output of previous operations.

A more complicated way would be to use thread states (Control.Monad.ST). They allow you to use mutable references and arrays inside the function, guaranteeing cleanliness on the outside.

+2
source

Source: https://habr.com/ru/post/907375/


All Articles