不幸的是,将“事件”和“行为”合并为单个实体“信号”效果并不那么好。
据我所知,大多数基于信号的 FRP 实现最终都会创建一个额外的“事件”类型,类似于
type Event a = Signal (Maybe a)
因此,事件的概念并没有消失,也没有真正的简化。事实上,我认为信号类型是语义的复杂化。信号之所以受欢迎只是因为它们更容易实现。
反对信号的主要论点是它们不能代表连续时间行为,因为它们必须迎合离散事件。在康纳尔·埃利奥特的最初的愿景 http://conal.net/papers/icfp97/,行为是时间的简单连续函数
type Behavior a = Time -> a
-- = function that takes the current time as parameter and returns
-- the corresponding value of type a
相反,信号始终是离散的,并且通常与固定的时间步长相关。 (有可能实施事件和行为都位于可变时间步信号之上,但它本身并不是一个很好的抽象。)将其与事件流进行比较
type Event a = [(Time,a)]
-- list of pairs of the form (current time, corresponding event value)
其中各个事件不一定以规则的时间间隔发生。
区分行为和事件的论据是它们的 API 完全不同。要点是它们有不同的产品类型:
(Behavior a , Behavior b) = Behavior (a,b)
(Event a , Event b ) = Event (a :+: b)
换句话说:一对行为与成对行为相同,但一对事件与来自任一组件/通道的事件相同。还有一点就是有两个操作
(<*>) :: Behavior (a -> b) -> Behavior a -> Behavior b
apply :: Behavior (a -> b) -> Event a -> Event b
它们具有几乎相同的类型,但语义却截然不同。 (第一个参数在第一个参数更改时更新结果,而第二个则不更新。)
总结一下:信号可用于实现 FRP,对于尝试新的实现技术很有价值,但对于只想使用 FRP 的人来说,行为和事件是更好的抽象。
(全面披露:我已经实现了一个名为的 FRP 库反应香蕉 http://haskell.org/haskellwiki/Reactive-banana在哈斯克尔。)