我正在模拟一个4位微处理器。我需要跟踪寄存器,内存和运行输出(还有一个获取 - 执行周期计数器的奖励点)。我已经设法在没有monad的情况下做到了这一点,但是在明确地传递那么多东西时感觉很乱。函数定义也很混乱,冗长且难以阅读。
我试图用monads做这个,但它不适合在一起。我尝试将所有单独的状态组件视为单一类型,但这给我留下了产生价值的问题。
State Program () -- Represents the state of the processor after a single iteration of the fetch execute cycle
是唯一有意义的类型。但那时为什么甚至打扰?我尝试通过从我的复合类型中拉出字符串并将其作为值
来分解它State Program' String
除了我需要RUNNING输出这一事实外,效果很好。无论我做了什么,我都无法同时保持弦乐和状态。
现在我正在努力解决monad变形金刚问题。似乎我必须将所有不同级别的州分开。但是我的脑袋快速爆炸。
StateT Registers (StateT Memory (State Output)) a =
StateT (registers -> (StateT Memory (State Output)) (a,registers))
StateT Registers (StateT Memory (State Output)) a =
StateT (registers -> (Memory -> (Output -> (((a,Registers),Memory),Output))))
我还没有进入FEcycle计数器!
问题:
答案 0 :(得分:9)
将多个状态monad叠加在一起是一个坏主意:你必须编写一堆lift
来获取每个状态,只能通过堆栈下面的层数来识别它是。呸!实际上,mtl库通常被设计为在极少数例外情况下使用堆栈中每个“种类”的一个monad变换器。
相反,我会建议StateT Program IO ()
。状态的界面是相同的,正如您所说,您可以使用IO
在liftIO
中输出。当然,值类型是()
,但是那有什么问题?您可以从顶级仿真器返回没有相关值。当然,您可能会将较小的可重用组件作为模拟器的 part ,并且这些组件将具有相关的结果类型。 (事实上,get
就是这样一个组成部分。)在顶层没有有意义的回报值没有错。
只要方便地访问州的每个部分,你要找的是镜头; this Stack Overflow answer是一个很好的介绍。它们让您可以轻松,轻松地访问和修改您所在州的独立部分。例如,使用data-lens实现,您可以轻松编写类似regA += 1
的内容来增加regA
,或者stack %= drop 2
来删除堆栈的前两个元素。
当然,它实际上是将您的代码转换为一组全局变量的命令性变异,但这实际上是一个优势,因为正好您正在仿效的CPU所基于的范例。使用data-lens-template包,您可以在一行中从记录定义中推导出这些镜头。
答案 1 :(得分:2)
执行此操作的一种简单方法是创建表示寄存器和内存的数据类型:
data Register = ...
data Memory = ...
data Machine = Machine [Register] Memory
然后有一些更新寄存器/内存的函数。现在将此类型用于您的州和类型的输出:
type Simulation = State Machine Output
现在每个操作都可以采用以下形式:
operation previous = do machine <- get
(result, newMachine) <- operate on machine
put newMachine
return result
此处previous
是机器的上一个输出。您也可以将其合并到结果中。
因此Machine
类型代表机器的状态;你正在通过它来处理先前操作的输出。
更复杂的方法是使用state threads(Control.Monad.ST)。这些允许您在函数内使用可变引用和数组,同时保证外部纯度。