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.
source share