如何在Haskell项目中组织大量状态

Nik*_*nni 11 haskell emulation

我正在编写我的第一个真正的Haskell项目,而且我在程序中组织状态时遇到了麻烦.它是一个Gameboy Color模拟器,因此有很多小标志,整个状态看起来像

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
                       }
Run Code Online (Sandbox Code Playgroud)

我做的所有读/写状态如下:

 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 ()
Run Code Online (Sandbox Code Playgroud)

你可以看到:https://github.com/nikhilunni/HaskellBoy/blob/master/src/Memory.hs

有没有更干净的方式来组织一切?如果可能的话,我想分解各种组件(CPU,GPU,盒式磁带组切换等)之间的状态.在Haskell中拥有一个庞大的单片状态是不是惯用的?

将新状态添加到程序中是一件非常痛苦的事情.Control.Lens包似乎是正确的小巷,但我不确定我是否可以很容易地将它与ST结合起来.

谢谢!

lef*_*out 5

镜片对于这种东西来说绝对是一个很好的帮助,但你宁愿在monad中使用一个大的,嵌套的状态对象State,而不是ST.而且我认为对于所有这些变量可能都没问题,尽管它可能不适用于数组(需要对每次修改进行深度复制).

所以我可以想到两个选择:

  • 从数组切换到具有高效纯功能更新的数据结构,如Sequence.STRefs完全抛弃这些,支持基于镜头的更新State.
    这将无法像破坏性阵列更新那样高效ST,但是为了在快速的现代计算机上模拟Game Boy,它可能会起作用.
  • 拆分内存类型以便可以保留数组ST,但将所有其他状态组合在STRef一个纯数据结构中.然后,你可以使用镜头.

    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
    
    Run Code Online (Sandbox Code Playgroud)

好处是,您现在可以将MemRef类型组合在一起,并使用镜头方便地向下伸入结构中.通过使结构更像树,更新实际上将变得更有效.(你可能也想拆除那些Word16和那些Bool字段,把这些小类型盒装起来真的很浪费.)

即便如此,您应该做好准备,这将不会像C++中的类似复杂的实现一样快.为了获得相当的性能,你可能不得不手工处理所有状态以使用所有状态信息编码的单一 STArray状态,并编写丑陋的OO风格的getter和setter ST以使其远程方便.