ArrowLoop 是如何工作的?另外,mfix?

发布于 2024-11-28 03:38:30 字数 1402 浏览 3 评论 0原文

我现在对箭头机械的其余部分相当满意,但我不明白循环是如何工作的。这对我来说似乎很神奇,但这不利于我的理解。我也很难理解 mfix。当我查看在 procdo 块中使用 rec 的代码时,我感到困惑。使用常规的单子或箭头代码,我可以逐步完成计算并保留脑海中正在发生的事情的操作画面。当我到达 rec 时,我不知道要保留什么图片!我陷入困境,无法推理这样的代码。

我试图理解的例子来自 Ross Paterson 关于箭头的论文,一篇关于电路的。

counter :: ArrowCircuit a => a Bool Int
counter = proc reset -> do
        rec     output <- returnA -< if reset then 0 else next
                next <- delay 0 -< output+1
        returnA -< output

我认为如果我理解这个示例,我将能够总体上理解循环,并且它将对理解 mfix 有很大帮助。我觉得它们本质上是一样的,但也许我错过了一些微妙之处?不管怎样,我真正想要的是这些代码片段的操作图片,这样我就可以像“常规”代码一样在脑海中逐步浏览它们。

编辑:感谢Pigworker的回答,我开始考虑rec以及诸如满足需求之类的问题。以 counter 为例,rec 块的第一行需要一个名为 output 的值。我想象这在操作上就像创建一个盒子,将其标记为输出,并要求rec块填充该盒子。为了填充该框,我们向 returnA 提供一个值,但该值本身需要另一个值,称为 next。为了使用这个值,必须要求记录块中的另一行但现在要求它在记录块中的哪个位置并不重要

因此,我们转到下一行,找到标记为 next 的框,并且我们要求另一个计算填充它。现在,这个计算需要我们的第一个盒子!所以我们给它盒子,但它里面没有任何值,所以如果这个计算需要输出的内容,我们就会陷入无限循环。幸运的是,延迟获取了盒子,但在不查看盒子内部的情况下产生了一个值。这将填充next,然后我们可以填充output。现在output已被填充,当处理该电路的下一个输入时,前一个output框将具有其值,准备好被要求以产生下一个< code>next,也就是下一个输出

听起来怎么样?

I'm fairly comfortable now with the rest of the arrow machinery, but I don't get how loop works. It seems magical to me, and that's bad for my understanding. I also have trouble understanding mfix. When I look at a piece of code that uses rec in a proc or do block, I get confused. With regular monadic or arrow code, I can step through the computation and keep an operational picture of what's going on in my head. When I get to rec, I don't know what picture to keep! I get stuck, and I can't reason about such code.

The example I'm trying to grok is from Ross Paterson's paper on arrows, the one about circuits.

counter :: ArrowCircuit a => a Bool Int
counter = proc reset -> do
        rec     output <- returnA -< if reset then 0 else next
                next <- delay 0 -< output+1
        returnA -< output

I assume that if I understand this example, I'll be able to understand loop in general, and it'll go a great way towards understanding mfix. They feel essentially the same to me, but perhaps there is a subtlety I'm missing? Anyway, what I would really prize is an operational picture of such pieces of code, so I can step through them in my head like 'regular' code.

Edit: Thanks to Pigworker's answer, I have started thinking about rec and such as demands being fulfilled. Taking the counter example, the first line of the rec block demands a value called output. I imagine this operationally as creating a box, labelling it output, and asking the rec block to fill that box. In order to fill that box, we feed in a value to returnA, but that value itself demands another value, called next. In order to use this value, it must be demanded of another line in the rec block but it doesn't matter where in the rec block it is demanded, for now.

So we go to the next line, and we find the box labelled next, and we demand that another computation fill it. Now, this computation demands our first box! So we give it the box, but it has no value inside it, so if this computation demands the contents of output, we hit an infinite loop. Fortunately, delay takes the box, but produces a value without looking inside the box. This fills next, which then allows us to fill output. Now that output is filled, when the next input of this circuit is processed, the previous output box will have its value, ready to be demanded in order to produce the next next, and thus the next output.

How does that sound?

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

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

发布评论

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

评论(1

天荒地未老 2024-12-05 03:38:30

在此代码中,关键部分是 rec 块中的 delay 0 箭头。为了了解它是如何工作的,将值视为随时间变化以及将时间切成片会有所帮助。我将切片视为“天”。 rec 块解释了每天的计算是如何进行的。它是按组织的,而不是按因果顺序组织的,但如果我们小心的话,我们仍然可以跟踪因果关系。至关重要的是,我们必须确保(在没有类型的任何帮助的情况下)每天的工作依赖于过去而不是未来。一天的delay 0在这方面为我们赢得了时间:它在一天后改变其输入信号,通过给出值0来处理第一天。延迟的输入信号是“明天的” >下一步'。

rec     output <- returnA -< if reset then 0 else next
        next <- delay 0 -< output+1

因此,查看箭头及其输出,我们将提供今天输出以及明天下一个。查看输入,我们依赖于今天resetnext值。很明显,我们可以从这些输入中提供这些输出,而无需时间旅行。除非我们重置为0,否则输出是今天的下一个数字;明天,下一个数字是今天输出的后继。因此,今天的 next 值来自昨天,除非昨天没有,在这种情况下它是 0。

在较低级别,由于 Haskell 的懒惰,整个设置都有效。 Haskell 通过需求驱动策略进行计算,因此如果存在尊重因果关系的任务顺序,Haskell 就会找到它。在这里,延迟建立了这样的顺序。

但请注意,Haskell 的类型系统在确保这种顺序存在方面几乎没有提供任何帮助。你可以随意使用循环来胡说八道!所以你的问题绝非微不足道。每次您阅读或编写这样的程序时,您确实需要思考“这怎么可能工作?”。您需要检查是否正确使用了延迟(或类似的),以确保仅在可以计算信息时才需要信息。请注意,构造函数,尤其是 (:) 也可以像延迟一样:计算列表的尾部并不罕见,显然给出了整个列表(但要小心)检查头部)。与命令式编程不同,惰性函数式风格允许您围绕事件顺序之外的概念来组织代码,但这种自由需要对时间有更微妙的认识。

In this code, they key piece is the delay 0 arrow in the rec block. To see how it works, it helps to think of values as varying over time and time as chopped into slices. I think of the slices as ‘days’. The rec block explains how each day's computation works. It's organised by value, rather than by causal order, but we can still track causality if we're careful. Crucially, we must make sure (without any help from the types) that each day's work relies on the past but not the future. The one-day delay 0 buys us time in that respect: it shifts its input signal one day later, taking care of the first day by giving the value 0. The delay's input signal is ‘tomorrow's next’.

rec     output <- returnA -< if reset then 0 else next
        next <- delay 0 -< output+1

So, looking at the arrows and their outputs, we're delivering today's output but tomorrow's next. Looking at the inputs, we're relying on today's reset and next values. It's clear that we can deliver those outputs from those inputs without time travel. The output is today's next number unless we reset to 0; tomorrow, the next number is the successor of today's output. Today's next value thus comes from yesterday, unless there was no yesterday, in which case it's 0.

At a lower level, this whole setup works because of Haskell's laziness. Haskell computes by a demand-driven strategy, so if there is a sequential order of tasks which respects causality, Haskell will find it. Here, the delay establishes such an order.

Be aware, though, that Haskell's type system gives you very little help in ensuring that such an order exists. You're free to use loops for utter nonsense! So your question is far from trivial. Each time you read or write such a program, you do need to think ‘how can this possibly work?’. You need to check that delay (or similar) is used appropriately to ensure that information is demanded only when it can be computed. Note that constructors, especially (:) can act like delays, too: it's not unusual to compute the tail of a list, apparently given the whole list (but being careful only to inspect the head). Unlike imperative programming, the lazy functional style allows you to organize your code around concepts other than the sequence of events, but it's a freedom that demands a more subtle awareness of time.

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