Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to organize large amounts of state in Haskell projects

I'm writing my first real Haskell project, and I'm having trouble organizing state in the program. It's a Gameboy Color emulator, so there are a lot of little flags, and the entire state looks like

data Memory s = Memory { memory :: STUArray s Word16 Word8
                       , registers :: STUArray s Word8 Word8
                       , sp :: STRef s Word16
                       , pc :: STRef s Word16
                       , cycles :: STRef s Word16
                       , ime :: STRef s Bool --Interrupt Master Enable Flag
                       , halt :: STRef s Bool --Are we halted or not
                       , mode :: STRef s GPUMode -- GPU mode
                       , line :: STRef s Word8 -- GPU line
                       , transferred :: STRef s Bool
                       , gpuCycles :: STRef s Word16
                       , window :: Window
                       , renderer :: Renderer
                       }

And I do all read/write of the state like :

 data Address = OneRegister Register
          | TwoRegister {registerA :: Register, registerB :: Register}
          | MemAddr Word16
          | SP
          | PC
          | CYCLES
          | IME
          | HALT_STATE
          | GPU_MODE
          | GPU_LINE
          | GPU_TRANSFERRED_LINE
          | GPU_CYCLES

  data MemVal = MemVal8 Word8
          | MemVal16 Word16
          | Flag Bool
          | Mode GPUMode

  read :: Memory s -> Address -> ST s MemVal
  write :: Memory s -> Address -> MemVal -> ST s ()

You can see : https://github.com/nikhilunni/HaskellBoy/blob/master/src/Memory.hs

Is there any cleaner way for me to organize everything? I'd like to split up the state between the various components (CPU, GPU, cartridge bank switching, etc), if possible. Is it idiomatic to have a big monolithic state type in Haskell?

It's a pretty big pain to add new state to the program. The Control.Lens package seems to be up the right alley, but I'm not sure if I can combine it with ST very easily.

Thanks!

like image 266
Nikhil Unni Avatar asked Jan 14 '16 04:01

Nikhil Unni


Video Answer


1 Answers

Lenses are definitely a great help for this kind of stuff, but you'd rather use them with a big, nested pure state object in a State monad, rather than ST. And I think that would probably be ok for all those variables, though it would probably not be acceptable for the arrays (which would need to be deep-copied with every modification.

So I could think of two options:

  • Switch from arrays to a data structure with efficient pure functional updates, like Sequence. Ditch those STRefs entirely, in favour of lens-based updates in State.
    That'll be nowhere as efficient as destructive array updates in ST, but for simulating a Game Boy on a fast modern computer it might just work.
  • Split up the memory type so you can keep the arrays in ST, but group all the other state in a single STRef to a pure data structure. On that you can then use lenses.

    data Memory s = Memory { memory :: STUArray s Word16 Word8
                           , registers :: STUArray s Word8 Word8
                           , memRefs :: STRef s MemRefs
                           , window :: Window
                           , renderer :: Renderer
                           }
    
    data MemRefs = MemRefs { _sp :: Word16
                           , _pc :: Word16
                           , _cycles :: Word16
                           , _ime :: Bool --Interrupt Master Enable Flag
                           , _halt :: Bool --Are we halted or not
                           , _mode :: GPUMode -- GPU mode
                           , _line :: Word8 -- GPU line
                           , _transferred :: Bool
                           , _gpuCycles :: Word16
                           }
    mkLenses ''MemRefs
    

The good thing is, you can now group the MemRef type al gusto, and use lenses to conveniently reach down into the structure. By making the structure more tree-like, the updates will actually become more efficient. (You probably also want to unbox those Word16 and Bool fields, it's really quite wasteful to keep such small types boxed.)

Even so, you should be prepared that this will not run as fast as a similarly complex implementation in, say, C++. To achieve comparable performance, you'd probably have to hand-taylor all that state to use a single STArray in which all the state information is encoded, and write ugly OO-style getters and setters in ST to make it remotely convenient.

like image 127
leftaroundabout Avatar answered Sep 21 '22 21:09

leftaroundabout