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

时间:2016-01-14 04:15:13

标签: 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
                       }

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

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

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

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

在程序中添加新状态真是太痛苦了。 Control.Lens软件包似乎是正确的小巷,但我不确定我是否可以很容易地将它与ST结合起来。

谢谢!

1 个答案:

答案 0 :(得分:5)

镜头对于这类东西肯定是一个很好的帮助,但是你宁愿在State monad中使用一个大的,嵌套的状态对象,而不是{{ 1}}。我认为对于所有这些变量可能都没问题,尽管它可能不适用于数组(需要在每次修改时进行深度复制。

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

  • 使用高效的纯功能更新(例如Sequence)从数组切换到数据结构。完全放弃那些ST,支持STRefs中基于镜头的更新。
    这将无法像State中的破坏性数组更新一样有效,但是用于模拟游戏快速的现代计算机上的男孩可能会工作。
  • 拆分内存类型,以便将数组保留在ST中,但将单个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 类型组合起来,并使用镜头方便地向下伸展到结构中。通过使结构更像树,更新实际上将变得更有效。 (您可能还想要取消隐藏这些MemRefWord16字段,将这些小类型设置为盒子非常浪费。)

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