简体   繁体   English

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

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

In recent implementations of Classic FRP, for instance reactive-banana, there are event streams and signals, which are step functions (reactive-banana calls them behaviours but they are nevertheless step functions). 在Classic FRP的最近实现中,例如反应性香蕉,存在事件流和信号,它们是阶梯函数(反应性香蕉称它们为行为,但它们仍然是阶梯函数)。 I've noticed that Elm only uses signals, and doesn't differentiate between signals and event streams. 我注意到Elm只使用信号,并没有区分信号和事件流。 Also, reactive-banana allows to go from event streams to signals (edited: and it's sort of possible to act on behaviours using reactimate' although it not considered good practice), which kind of means that in theory we could apply all the event stream combinators on signals/behaviours by first converting the signal to event stream, applying and then converting again. 此外,反应性香蕉允许从事件流转变为信号(编辑:并且可以使用重新作用对行为采取行动'虽然不被认为是良好做法),这意味着理论上我们可以应用所有事件流通过首先将信号转换为事件流,应用然后再次转换,对信号/行为进行组合。 So, given that it's in general easier to use and learn just one abstraction, what is the advantage of having separated signals and event streams ? 因此,鉴于它通常更容易使用并只学习一个抽象,分离信号和事件流的优势是什么? Is anything lost in using just signals and converting all the event stream combinators to operate on signals ? 在使用信号和转换所有事件流组合器以对信号进行操作时是否有任何损失?

edit: The discussion has been very interesting. 编辑:讨论非常有趣。 The main conclusions that I took from the discussion myself is that behaviours/event sources are both needed for mutually recursive definitions (feedback) and for having an output depend on two inputs (a behaviour and an event source) but only cause an action when one of them changes (<@>). 我自己讨论的主要结论是,行为/事件源既需要相互递归的定义(反馈),也需要输出依赖于两个输入(一个行为和一个事件源),但只在一个时产生一个动作他们改变了(<@>)。

(Clarification: In reactive-banana, it is not possible to convert a Behavior back to an Event . The stepper function is a one-way ticket. There is a changes function, but its type indicates that it is "impure" and it comes with a warning that it does not preserve the semantics.) (澄清:在反应香蕉中,不可能将Behavior转换回Eventstepper功能是单程票。有一个changes功能,但它的类型表明它是“不纯的”它来了警告说它不保留语义。)

I believe that having two separates concepts makes the API more elegant. 我相信有两个独立的概念使API更优雅。 In other words, it boils down to a question of API usability. 换句话说,它归结为API可用性的问题。 I think that the two concepts behave sufficiently differently that things flow better if you have two separate types. 我认为这两个概念的行为完全不同,如果你有两种不同的类型,事情会更好地流动。

For example, the direct product for each type is different. 例如,每种类型的直接产品是不同的。 A pair of Behavior is equivalent to a Behavior of pairs 一对行为相当于一对行为

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

whereas a pair of Events is equivalent to an Event of a direct sum : 而一对事件相当于直接的事件:

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

If you merge both types into one, then neither of these equivalence will hold anymore. 如果将两种类型合并为一种,那么这些等价都不会再存在。

However, one of the main reasons for the separation of Event and Behavior is that the latter does not have a notion of changes or "updates". 然而,主要的原因的事件和行为的分离之一是,后者没有 改变或“更新”的概念。 This may seem like an omission at first, but it is extremely useful in practice, because it leads to simpler code. 这看起来似乎是一个遗漏,但它在实践中非常有用,因为它会导致更简单的代码。 For instance, consider a monadic function newInput that creates an input GUI widget that displays the text indicated in the argument Behavior, 例如,考虑一个newInput函数newInput ,它创建一个输入GUI小部件,显示参数Behavior中指示的文本,

input <- newInput (bText :: Behavior String)

The key point now is that the text displayed does not depend on how often the Behavior bText may have been updated (to the same or a different value), only on the actual value itself. 关键的一点是现在的文字显示取决于多久行为bText可能已更新(相同或不同的值),只在本身的实际价值。 This is a lot easier to reason about than the other case, where you would have to think about what happens when two successive event occurrences have the same value. 这比其他情况更容易推理,在这种情况下,您必须考虑当两个连续的事件发生具有相同值时会发生什么。 Do you redraw the text while the user edits it? 在用户编辑文本时是否重绘文本?

(Of course, in order to actually draw the text, the library has to interface with the GUI framework and does keep track of changes in the Behavior. This is what the changes combinator is for. However, this can be seen as an optimization and is not available from "within FRP".) (当然,为了实际绘制文本,库必须与GUI框架接口并跟踪行为的变化。这就是组合changes的用途。但是,这可以看作是一种优化和不在“FRP内”。)

The other main reason for the separation is recursion . 分离的另一个主要原因是递归 Most Events that recursively depend on themselves are ill-defined. 递归依赖于自身的大多数事件都是不明确的。 However, recursion is always allowed if you have mutual recursion between an Event and a Behavior 但是,如果事件和行为之间存在相互递归,则始终允许递归

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

There is no need to introduce delays by hand, it just works out of the box. 没有必要手工引入延迟,它只是开箱即用。

Something critically important to me is lost, namely the essence of behaviors, which is (possibly continuous) variation over continuous time. 对我来说至关重要的东西就会丢失,即行为的本质,​​即连续时间的变化(可能是连续的)。 Precise, simple, useful semantics (independent of a particular implementation or execution) is often lost as well. 精确,简单,有用的语义(独立于特定的实现或执行)通常也会丢失。 Check out my answer to "Specification for a Functional Reactive Programming language", and follow the links there. 查看对“功能反应式编程语言规范”的回答 ,并点击那里的链接。

Whether in time or in space, premature discretization thwarts composability and complicates semantics. 无论是在时间上还是在太空中,过早的离散化都会阻碍可组合性并使语义复杂化。 Consider vector graphics (and other spatially continuous models like Pan 's). 考虑矢量图形(和的其他空间连续模型)。 Just as with premature finitization of data structures as explained in Why Functional Programming Matters . 正如“功能编程为何重要”中所解释的那样,数据结构的过早完成也是如此。

I don't think there's any benefit to using the signals/behaviors abstraction over elm-style signals. 我认为使用信号/行为抽象而不是榆树式信号有任何好处。 As you point out, it's possible to create a signal-only API on top of the signal/behavior API (not at all ready for use, but see https://github.com/JohnLato/impulse/blob/dyn2/src/Reactive/Impulse/Syntax2.hs for an example). 正如您所指出的,可以在信号/行为API之上创建一个仅信号API(完全没有准备好使用,但请参阅https://github.com/JohnLato/impulse/blob/dyn2/src/ Reactive / Impulse / Syntax2.hs为例)。 I'm pretty sure it's also possible to write a signal/behavior API on top of an elm-style API as well. 我很确定也可以在elm风格的API之上编写信号/行为API。 That would make the two APIs functionally equivalent. 这将使两个API功能相同。

WRT efficiency, with a signals-only API the system should have a mechanism where only signals that have updated values will cause recomputations (eg if you don't move the mouse, the FRP network won't re-calculate the pointer coordinates and redraw the screen). WRT效率,使用仅信号API,系统应该有一种机制,其中只有具有更新值的信号才会导致重新计算(例如,如果不移动鼠标,FRP网络将不会重新计算指针坐标并重绘屏幕)。 Provided this is done, I don't think there's any loss of efficiency compared to a signals-and-streams approach. 如果这样做,我认为与信号和流方法相比,效率没有任何损失。 I'm pretty sure Elm works this way. 我很确定榆树这样工作。

I don't think the continuous-behavior issue makes any difference here (or really at all). 我认为持续行为问题在这里(或者根本没有)会产生任何不同。 What people mean by saying behaviors are continuous over time is that they are defined at all times (ie they're functions over a continuous domain); 人们所说的行为随着时间的推移是连续的意思是它们始终被定义(即它们是连续领域的功能); the behavior itself isn't a continuous function. 行为本身不是一个连续的功能。 But we don't actually have a way to sample a behavior at any time; 但我们实际上并没有办法随时对行为进行抽样; they can only be sampled at times corresponding to events, so we can't use the full power of this definition! 它们只能在与事件相对应的时间进行采样,因此我们无法使用此定义的全部功能!

Semantically, starting from these definitions: 从语义上讲,从这些定义开始:

Event    == for some t ∈ T: [(t,a)]
Behavior == ∀ t ∈ T: t -> b

since behaviors can only be sampled at times where events are defined, we can create a new domain TX where TX is the set of all times t at which Events are defined. 由于行为只能在定义事件时进行采样,因此我们可以创建一个新的域TX ,其中TX是定义事件的所有时间t的集合。 Now we can loosen the Behavior definition to 现在我们可以将行为定义放宽到

Behavior == ∀ t ∈ TX: t -> b

without losing any power (ie this is equivalent to the original definition within the confines of our frp system). 不失任何力量(即这相当于我们的frp系统范围内的原始定义)。 Now we can enumerate all times in TX to transform this to 现在我们可以枚举TX所有时间来将其转换为

Behavior == ∀ t ∈ TX: [(t,b)]

which is identical to the original Event definition except for the domain and quantification. 除了域和量化之外,它与原始Event定义相同。 Now we can change the domain of Event to TX (by the definition of TX ), and the quantification of Behavior (from forall to for some) and we get 现在,我们可以改变的域EventTX (由定义TX ),和量化Behavior (从FORALL为一些),我们得到

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

and now Event and Behavior are semantically identical, so they could obviously be represented using the same structure in an FRP system. 现在EventBehavior在语义上是相同的,因此它们显然可以在FRP系统中使用相同的结构来表示。 We do lose a bit of information at this step; 我们确实在这一步失去了一些信息; if we don't differentiate between Event and Behavior we don't know that a Behavior is defined at every time t , but in practice I don't think this really matters much. 如果我们不区分EventBehavior我们不知道每次 t都定义了一个Behavior ,但实际上我并不认为这真的很重要。 What elm does IIRC is require both Event s and Behavior s to have values at all times and just use the previous value for an Event if it hasn't changed (ie change the quantification of Event to forall instead of changing the quantification of Behavior ). 榆树IIRC的作用是要求EventBehavior s始终具有值,如果Event没有改变,只需使用Event的前一个值(即将Event的量化改为forall而不是改变Behavior的量化) 。 This means you can treat everything as a signal and it all Just Works; 这意味着你可以将所有东西都当作一个信号对待,而这一切都是Just Works it's just implemented so that the signal domain is exactly the subset of time that the system actually uses. 它刚刚实现,因此信号域正是系统实际使用的时间子集。

I think this idea was presented in a paper (which I can't find now, anyone else have a link?) about implementing FRP in Java, perhaps from POPL '14? 我认为这个想法是在一篇论文中提出的(我现在找不到,其他人都有链接?)关于在Java中实现FRP,也许是从POPL '14开始? Working from memory, so my outline isn't as rigorous as the original proof. 从记忆中工作,所以我的轮廓不像原始证明那么严谨。

There's nothing to stop you from creating a more-defined Behavior by eg pure someFunction , this just means that within an FRP system you can't make use of that extra defined-ness, so nothing is lost by a more restricted implementation. 没有什么可以阻止你通过例如pure someFunction创建一个更明确的Behavior ,这只是意味着在FRP系统中你不能利用那个额外的定义,所以没有任何东西因更受限制的实现而丢失。

As for notional signals such as time, note that it's impossible to implement an actual continuous-time signal using typical programming languages. 对于诸如时间之类的概念信号,请注意,使用典型的编程语言不可能实现实际的连续时间信号。 Since the implementation will necessarily be discrete, converting that to an event stream is trivial. 由于实现必然是离散的,因此将其转换为事件流是微不足道的。

In short, I don't think anything is lost by using just signals. 简而言之,我认为只使用信号就不会丢失任何东西。

I've unfortunately have no references in mind, but I distinctly remember different reactive authors claiming this choice is just for efficiency. 遗憾的是,我没有任何参考文献,但我清楚地记得不同的反应作者声称这种选择只是为了提高效率。 You expose both to give the programmer a choice in what implementation of the same idea is more efficient for your problem. 您将两者都公开,以便程序员可以选择相同构思的实现对您的问题更有效。

I might be lying now, but I believe Elm implements everything as event streams under the hood. 我现在可能在撒谎,但我相信Elm将所有事情都当作事件流来实现。 Things like time wouldn't be so nice like event streams though, since there are an infinite amount of events during any time frame. 像时间这样的事情不会像事件流那么好,因为在任何时间范围内都有无数的事件。 I'm not sure how Elm solves this, but I think it's a good example on something that makes more sense as a signal, both conceptually and in implementation. 我不确定Elm是如何解决这个问题的,但我认为这是一个很好的例子,它可以在概念和实现方面作为一种信号更有意义。

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

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