Подтвердить что ты не робот

Как организовать большие объемы штата в проектах Haskell

Я пишу свой первый настоящий проект 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.

Спасибо!

4b9b3361

Ответ 1

Линзы, безусловно, являются большой помощью для такого рода вещей, но вы предпочтете использовать их с большим, вложенным объектом чистого состояния в монаде 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
    

Хорошо, теперь вы можете группировать стиль MemRef типа al gusto и использовать линзы, чтобы удобно дойти до структуры. Сделав структуру более древовидной, обновления станут более эффективными. (Вероятно, вы также захотите распаковать те поля Word16 и Bool, это действительно очень расточительно, чтобы хранить такие маленькие типы в коробке.)

Тем не менее, вы должны быть готовы к тому, что это не будет работать так же быстро, как аналогично сложная реализация, скажем, в С++. Чтобы достичь сопоставимой производительности, вам, вероятно, придется использовать все это состояние для использования одного STArray, в котором закодирована вся информация о состоянии, и писать уродливые гейтеры и сеттеры в стиле OO в ST, чтобы сделать его удаленно удобно.