编辑:讨论非常有趣。我自己得出的主要结论是,behaviours/event sources都需要互相递归定义(反馈)并且输出取决于两个输入(一个behaviour和一个event source),但仅当其中一个更改时才会引起动作(<@>)。
Behavior
转换为Event
。 stepper
函数是一个单向门票。有一个changes
函数,但其类型表明它是“不纯的”,并且带有一个警告,说明它不能保留语义。)(Behavior a, Behavior b) ~ Behavior (a,b)
一对事件相当于一个直接和的事件:
(Event a, Event b) ~ Event (EitherOrBoth a b)
如果您将这两种类型合并为一种类型,则这些等价关系都将不再成立。newInput
,它创建一个输入GUI小部件,显示参数行为中指定的文本。input <- newInput (bText :: Behavior String)
现在关键的一点是,显示的文本并不取决于行为 bText 更新了多少次(无论是更新为相同的值还是不同的值),而只取决于实际的值本身。这比另一种情况容易理解得多,因为在另一种情况下,您必须考虑当两个连续事件发生具有相同值时会发生什么。在用户编辑文本时重新绘制文本吗?e = ((+) <$> b) <@> einput
b = stepper 0 e
不需要手动引入延迟,它直接可用。
apply
组合器。这是一件好事,因为它可以避免你去考虑行为已经更新但其值仍然相同的情况。 - Heinrich Apfelmusreactimate'
仍然存在问题,因为它允许您观察破坏语义的事件。您需要确保在某种意义上IO
操作是幂等的。我的意思是,这不会伤害任何人,但证明的责任在于您而不是库。我没有将changes
与reactimate'
组合,因为在某些非常特定的情况下,您希望将从changes
获取的Event
与其他内容合并。 - Heinrich Apfelmus对我来说非常重要的是失去了行为的本质,即在连续时间上(可能是持续的)变化。 精确、简单、有用的语义(独立于特定实现或执行)经常也会丢失。 请查看我的回答“Specification for a Functional Reactive Programming language”并按照其中的链接进行跟踪。
无论是在时间还是空间上,过早的离散化妨碍了可组合性并使语义复杂化。考虑矢量图形(以及其他像Pan的空间连续模型)。就像数据结构的过早有限化一样,如Why Functional Programming Matters中所解释的那样。
Event == for some t ∈ T: [(t,a)]
Behavior == ∀ t ∈ T: t -> b
由于行为只能在定义事件的时间进行采样,我们可以创建一个新领域 TX
,其中 TX
是所有定义事件的时间 t
的集合。现在我们可以放宽行为定义为:
Behavior == ∀ t ∈ TX: t -> b
在不损失任何功率的情况下(即在我们的FRP系统限制范围内,这等同于原始定义),现在我们可以列举TX中的所有时间,将其转换为:
Behavior == ∀ t ∈ TX: [(t,b)]
这与原始的Event
定义完全相同,除了领域和量化。现在我们可以根据TX
的定义更改Event
的领域,并将Behavior
的量化(从forall变为for some),我们就得到了:
Event == for some t ∈ TX: [(t,a)]
Behavior == for some t ∈ TX: [(t,b)]
Event
和Behavior
在语义上是相同的,因此在FRP系统中可以使用相同的结构来表示它们。在这一步骤中我们会失去一些信息;如果我们不区分Event
和Behavior
,我们就不知道Behavior
在每个时间点t
都被定义了,但实际上我认为这并不重要。据我所知,elm需要Event
和Behavior
在所有时间点上都有值,并且如果一个Event
没有更改,就使用先前的值(即将Event
的量化改变为forall
而不是改变Behavior
的量化)。这意味着您可以将所有内容都视为信号,并且所有内容都可以正常工作;只是实现时,信号域恰好是系统实际使用的时间子集。
我认为这个想法是在一篇论文中提出的(我现在找不到了,还有人可以提供链接吗?),关于在Java中实现FRP,也许是来自POPL'14?由于是靠记忆回忆的,所以我的概述并不像原始证明那样严格。
并没有什么可以阻止您通过例如pure someFunction
来创建更明确定义的Behavior
,这仅意味着在FRP系统中无法利用那个额外的定义,因此通过更受限制的实现不会失去任何东西。
至于时间等概念信号,注意使用典型编程语言无法实现实际连续时间信号。由于实现必然是离散的,将其转换为事件流是微不足道的。
简而言之,我认为只使用信号不会失去任何东西。
[(t, t->b)]
会更好。 - Bergi很遗憾我没有具体的参考资料,但我清楚地记得不同的反应式作者声称这种选择只是为了提高效率。你暴露出两个选项,让程序员可以选择哪种实现方式更适合解决问题。
我可能现在在撒谎,但我相信 Elm 在内部将所有内容都实现为事件流。像时间这样的东西就不会像事件流那样好用,因为在任何时间段内都有无限多的事件。我不确定 Elm 是如何解决这个问题的,但我认为这是一个很好的例子,说明某些概念在概念上和实现上更适合作为信号。