繁体   English   中英

FRP - 事件流和信号 - 使用信号时会丢失什么?

[英]FRP - Event streams and Signals - what is lost in using just signals?

在Classic FRP的最近实现中,例如反应性香蕉,存在事件流和信号,它们是阶梯函数(反应性香蕉称它们为行为,但它们仍然是阶梯函数)。 我注意到Elm只使用信号,并没有区分信号和事件流。 此外,反应性香蕉允许从事件流转变为信号(编辑:并且可以使用重新作用对行为采取行动'虽然不被认为是良好做法),这意味着理论上我们可以应用所有事件流通过首先将信号转换为事件流,应用然后再次转换,对信号/行为进行组合。 因此,鉴于它通常更容易使用并只学习一个抽象,分离信号和事件流的优势是什么? 在使用信号和转换所有事件流组合器以对信号进行操作时是否有任何损失?

编辑:讨论非常有趣。 我自己讨论的主要结论是,行为/事件源既需要相互递归的定义(反馈),也需要输出依赖于两个输入(一个行为和一个事件源),但只在一个时产生一个动作他们改变了(<@>)。

(澄清:在反应香蕉中,不可能将Behavior转换回Eventstepper功能是单程票。有一个changes功能,但它的类型表明它是“不纯的”它来了警告说它不保留语义。)

我相信有两个独立的概念使API更优雅。 换句话说,它归结为API可用性的问题。 我认为这两个概念的行为完全不同,如果你有两种不同的类型,事情会更好地流动。

例如,每种类型的直接产品是不同的。 一对行为相当于一对行为

(Behavior a, Behavior b) ~ Behavior (a,b)

而一对事件相当于直接的事件:

(Event    a, Event    b) ~ Event (EitherOrBoth a b)

如果将两种类型合并为一种,那么这些等价都不会再存在。

然而,主要的原因的事件和行为的分离之一是,后者没有 改变或“更新”的概念。 这看起来似乎是一个遗漏,但它在实践中非常有用,因为它会导致更简单的代码。 例如,考虑一个newInput函数newInput ,它创建一个输入GUI小部件,显示参数Behavior中指示的文本,

input <- newInput (bText :: Behavior String)

关键的一点是现在的文字显示取决于多久行为bText可能已更新(相同或不同的值),只在本身的实际价值。 这比其他情况更容易推理,在这种情况下,您必须考虑当两个连续的事件发生具有相同值时会发生什么。 在用户编辑文本时是否重绘文本?

(当然,为了实际绘制文本,库必须与GUI框架接口并跟踪行为的变化。这就是组合changes的用途。但是,这可以看作是一种优化和不在“FRP内”。)

分离的另一个主要原因是递归 递归依赖于自身的大多数事件都是不明确的。 但是,如果事件和行为之间存在相互递归,则始终允许递归

e = ((+) <$> b) <@> einput
b = stepper 0 e

没有必要手工引入延迟,它只是开箱即用。

对我来说至关重要的东西就会丢失,即行为的本质,​​即连续时间的变化(可能是连续的)。 精确,简单,有用的语义(独立于特定的实现或执行)通常也会丢失。 查看对“功能反应式编程语言规范”的回答 ,并点击那里的链接。

无论是在时间上还是在太空中,过早的离散化都会阻碍可组合性并使语义复杂化。 考虑矢量图形(和的其他空间连续模型)。 正如“功能编程为何重要”中所解释的那样,数据结构的过早完成也是如此。

我认为使用信号/行为抽象而不是榆树式信号有任何好处。 正如您所指出的,可以在信号/行为API之上创建一个仅信号API(完全没有准备好使用,但请参阅https://github.com/JohnLato/impulse/blob/dyn2/src/ Reactive / Impulse / Syntax2.hs为例)。 我很确定也可以在elm风格的API之上编写信号/行为API。 这将使两个API功能相同。

WRT效率,使用仅信号API,系统应该有一种机制,其中只有具有更新值的信号才会导致重新计算(例如,如果不移动鼠标,FRP网络将不会重新计算指针坐标并重绘屏幕)。 如果这样做,我认为与信号和流方法相比,效率没有任何损失。 我很确定榆树这样工作。

我认为持续行为问题在这里(或者根本没有)会产生任何不同。 人们所说的行为随着时间的推移是连续的意思是它们始终被定义(即它们是连续领域的功能); 行为本身不是一个连续的功能。 但我们实际上并没有办法随时对行为进行抽样; 它们只能在与事件相对应的时间进行采样,因此我们无法使用此定义的全部功能!

从语义上讲,从这些定义开始:

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定义相同。 现在,我们可以改变的域EventTX (由定义TX ),和量化Behavior (从FORALL为一些),我们得到

Event    == for some t ∈ TX: [(t,a)]
Behavior == for some t ∈ TX: [(t,b)]

现在EventBehavior在语义上是相同的,因此它们显然可以在FRP系统中使用相同的结构来表示。 我们确实在这一步失去了一些信息; 如果我们不区分EventBehavior我们不知道每次 t都定义了一个Behavior ,但实际上我并不认为这真的很重要。 榆树IIRC的作用是要求EventBehavior s始终具有值,如果Event没有改变,只需使用Event的前一个值(即将Event的量化改为forall而不是改变Behavior的量化) 。 这意味着你可以将所有东西都当作一个信号对待,而这一切都是Just Works 它刚刚实现,因此信号域正是系统实际使用的时间子集。

我认为这个想法是在一篇论文中提出的(我现在找不到,其他人都有链接?)关于在Java中实现FRP,也许是从POPL '14开始? 从记忆中工作,所以我的轮廓不像原始证明那么严谨。

没有什么可以阻止你通过例如pure someFunction创建一个更明确的Behavior ,这只是意味着在FRP系统中你不能利用那个额外的定义,所以没有任何东西因更受限制的实现而丢失。

对于诸如时间之类的概念信号,请注意,使用典型的编程语言不可能实现实际的连续时间信号。 由于实现必然是离散的,因此将其转换为事件流是微不足道的。

简而言之,我认为只使用信号就不会丢失任何东西。

遗憾的是,我没有任何参考文献,但我清楚地记得不同的反应作者声称这种选择只是为了提高效率。 您将两者都公开,以便程序员可以选择相同构思的实现对您的问题更有效。

我现在可能在撒谎,但我相信Elm将所有事情都当作事件流来实现。 像时间这样的事情不会像事件流那么好,因为在任何时间范围内都有无数的事件。 我不确定Elm是如何解决这个问题的,但我认为这是一个很好的例子,它可以在概念和实现方面作为一种信号更有意义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM