31

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

我试图理解的例子来自罗斯帕特森关于箭头的论文,关于电路的论文。

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。我想象这在操作上是创建一个盒子,给它贴标签output,然后让 rec 块填充那个盒子。为了填充该框,我们向 returnA 输入一个值,但该值本身需要另一个值,称为next. 为了使用这个值,必须在 rec 块中要求另一行,但现在在 rec 块中的哪个位置要求它并不重要

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

听起来怎么样?

4

1 回答 1

27

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

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

因此,查看箭头及其输出,我们正在交付今天 output明天的 next. 查看输入,我们依赖于今天的 reset价值观next。很明显,我们可以在没有时间旅行的情况下从这些输入中提供这些输出。output是今天的数字next,除非我们reset为 0;明天的next号码就是今天的接班人output。因此,今天的next值来自昨天,除非没有昨天,在这种情况下它是 0。

在较低级别上,由于 Haskell 的懒惰,整个设置都可以正常工作。Haskell 通过需求驱动的策略进行计算,因此如果存在尊重因果关系的任务顺序,Haskell 会找到它。在这里,delay建立这样一个命令。

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

于 2011-08-08T09:49:15.833 回答