我正在编写我的第一个真正的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结合起来。
谢谢!
答案 0 :(得分:5)
镜头对于这类东西肯定是一个很好的帮助,但是你宁愿在State
monad中使用一个大的,嵌套的纯状态对象,而不是{{ 1}}。我认为对于所有这些变量可能都没问题,尽管它可能不适用于数组(需要在每次修改时进行深度复制。
所以我可以想到两个选择:
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
类型组合起来,并使用镜头方便地向下伸展到结构中。通过使结构更像树,更新实际上将变得更有效。 (您可能还想要取消隐藏这些MemRef
和Word16
字段,将这些小类型设置为盒子非常浪费。)
即便如此,你应该做好准备,这不会像C ++中的类似复杂的实现一样快。为了获得相当的性能,你可能不得不手动使用所有状态来使用单个 Bool
,其中所有的状态信息都被编码,并写出丑陋的OO风格的getter和STArray
中的setter使其远程方便。