ArrowLoop如何运作?还有,mfix?

时间:2011-08-08 01:11:37

标签: haskell monads arrows

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

我试图解决的例子来自Ross Paterson's paper on arrows,关于电路。

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的方框,我们要求另一个计算填充它。现在,这个计算需要我们的第一个盒子!所以我们给它框,但它里面没有任何值,所以如果这个计算需要output的内容,我们就会遇到无限循环。幸运的是,延迟占用了盒子,但产生了一个值而没有查看盒子内部。这会填充next,然后我们可以填充output。现在output被填充,当处理该电路的下一个输入时,先前的output框将具有其值,准备被要求以产生下一个next,并且因此下一个output

听起来怎么样?

1 个答案:

答案 0 :(得分:25)

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

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

所以,看看箭头及其输出,我们正在提供今天的 output,但明天的 next。查看输入,我们依赖于今天的 resetnext值。很明显,我们可以在没有时间旅行的情况下从这些输入中提供这些输出。除非我们output为0,否则next是今天的reset数字;明天,next号码是今天output的继承者。今天的next值因此来自昨天,除非昨天没有,在这种情况下它是0。

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

但请注意,Haskell的类型系统在确保存在此类订单方面几乎没有帮助。你可以自由地使用循环来完全废话!所以你的问题远非微不足道。每次阅读或编写此类程序时,您都需要考虑“这可能如何工作?”。您需要检查是否正确使用delay(或类似),以确保仅在可以计算信息时才需要信息。请注意,构造函数,特别是(:)也可以像延迟一样:计算列表的尾部并不常见,显然是给出了整个列表(但是只注意检查头部) 。与命令式编程不同,懒惰的功能风格允许您围绕事件序列以外的概念组织代码,但这是一种需要更细微的时间意识的自由。