Haskell 中不同的、交互的状态级别

发布于 2025-01-01 02:55:12 字数 1108 浏览 4 评论 0原文

我正在模拟 4 位微处理器。我需要跟踪寄存器、内存和运行输出(还有一个获取执行周期计数器的奖励点)。我已经成功地在没有 monad 的情况下做到了这一点,但同时明确地传递这么多东西感觉很混乱。而且函数定义很乱、很长并且难以阅读。

我尝试用 monad 来做到这一点,但它只是不适合在一起。我尝试将所有单独的状态组件视为单一类型,但这给我留下了如何生成值的问题。

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 计数器!

问题:

  1. 我走在正确的道路上吗?
  2. 鉴于我现在正在拔出 monad 变压器,是否可以停止将“运行输出”视为状态并将其交给 IO monad?那太棒了,我可以打印它,而不是保留它。
  3. 我应该将状态分为几层?我可以看到两个不同的层,但它们彼此紧密依赖(内存和寄存器都依赖于内存和寄存器的状态)。我应该将它们作为一个状态保持在一起还是将它们分开并堆叠起来?哪种方法会产生最具可读性的代码?

I'm emulating a 4 bit microprocessor. I need to keep track of the registers, the memory and the running output (bonus points for also having a fetch-execute cycle counter). I've managed to do this without monads, but it feels messy passing around that much stuff at once explicitly. Also the function definition is messy, long and hard to read.

I've tried to do this with monads and it just doesn't fit together. I tried treating all the separate state components as a single type, but that left me with the problem of what to make the value.

State Program () -- Represents the state of the processor after a single iteration of the fetch execute cycle

Was the only type that made any sense. But at that point why even bother? I tried breaking it up by pulling the string out of my composite type and treating it as the value

State Program' String

which worked great, except for the fact that I needed RUNNING output. No matter what I did I couldn't hold on to both the string and the state at the same time.

Now I'm trying to grapple with monad transformers. It seems like I have to separate out all the different levels of state. But my head is exploding fast.

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

I haven't even put in the FEcycle counter yet!

Questions:

  1. Am I on the right track?
  2. Seeing as I'm pulling out monad transformers now, is it possible to stop treating "running output" as state and just palm it off to the IO monad? That would be awesome, instead of holding on to it I could just print it.
  3. How many layers should I separate the state into? I can see two distinct layers, but they depend on each other closely (both the memory and the registers depend on the state of both the memory and the registers). Should I keep them together as a single state or separate them out and stack them up? Which approach will produce the most readable code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

吻风 2025-01-08 02:55:13

将多个状态单子分层是一个坏主意:您必须组合一堆 lift 来获取每个状态,仅通过堆栈中的层数来识别是。哎呀!事实上,除了极少数例外,mtl 库通常被设计为与堆栈中的每种“种类”有一个 monad 转换器一起使用。

相反,我建议使用 StateT Program IO ()。状态接口是相同的,正如您所说,您可以简单地使用 liftIOIO 中进行输出。当然,值类型是 (),但这有什么问题呢?您无法从顶级模拟器返回任何相关值。当然,您可能会将更小的、可重用的组件作为模拟器的一部分,并且这些组件将具有相关的结果类型。 (事实上​​,get 就是这样的组件之一。)顶层没有有意义的返回值并没有什么问题。

为了方便地前往该州的各个地方,您需要的是镜片; 这个 Stack Overflow 答案 是一个很好的介绍。它们使您可以简单轻松地访问和修改状态的独立部分。例如,使用 data-lens 实现,您可以轻松编写类似 regA += 1 增加 regA,或 stack %= drop 2 删除堆栈的前两个元素。

当然,它本质上是将您的代码转变为一组全局变量的命令式突变,但这实际上是一个优势,因为这正是您正在模拟的 CPU 所基于的范例。 a href="http://hackage.haskell.org/package/data-lens-template" rel="nofollow noreferrer">data-lens-template 包,您可以从记录定义中派生这些镜头一行。

Layering multiple state monads on top of each other is a bad idea: you will have to compose a bunch of lifts to get at each piece of state, identified only by the number of layers down the stack it is. Yuck! Indeed, the mtl library in general is designed to be used, with rare exceptions, with one monad transformer of each "kind" in a stack.

Instead, I would suggest StateT Program IO (). The interface to the state is the same and you can, as you said, do output in IO simply by using liftIO. Sure, the value type is (), but what's wrong with that? There's no relevant value you can return from the top-level emulator. And, of course, you're likely to have smaller, reusable components as part of your emulator, and those will have relevant result types. (Indeed, get is one such component.) There's nothing wrong with having no meaningful return value at the top level.

As far as conveniently accessing each portion of the state, what you're looking for is lenses; this Stack Overflow answer is an excellent introduction. They let you simply and easily access and modify independent parts of your state. For example, with the data-lens implementation, you can easily write something like regA += 1 to increment regA, or stack %= drop 2 to remove the first two elements of the stack.

Sure, it's essentially turning your code into imperative mutation of a set of global variables, but this is actually an advantage, since that's exactly the paradigm the CPU you're emulating is based in. And with the data-lens-template package, you can derive these lenses from a record definition in a single line.

一袭水袖舞倾城 2025-01-08 02:55:13

一种简单的方法是创建一个表示寄存器和内存的数据类型:

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 类型代表机器的状态;您正在通过它线程化先前操作的输出。

更复杂的方法是使用状态线程 (Control.Monad.ST)。这些允许您在函数内部使用可变引用和数组,同时保证外部的纯度。

A simple way to do this would be to create a data type that represents the registers and memory:

data Register = ...
data Memory = ...
data Machine = Machine [Register] Memory

Then have some functions that update the registers/memory. Now use this type for your state and the output for your type:

type Simulation = State Machine Output

Now each operation could be in the form:

operation previous = do machine <- get
                        (result, newMachine) <- operate on machine
                        put newMachine
                        return result

Here previous is the previous output of the machine. You can incorporate it into the result as well.

So the Machine type represents the state of the machine; you're threading the output of the previous operations through it.

A more sophisticated way would be to use state threads (Control.Monad.ST). These let you use mutable references and arrays inside a function while being guaranteed purity on the outside.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文