不幸的是,将“事件”和“行为”合并成一个单一的实体“信号”并不能很好地工作。
我知道的大多数基于信号的 FRP 实现最终都会创建一个额外的类似“事件”的类型
type Event a = Signal (Maybe a)
所以,事件的概念并没有消失,也没有真正的简化。事实上,我认为信号类型是语义上的复杂化。信号之所以流行只是因为它们更容易实现。
反对信号的主要论点是它们不能代表连续的时间行为,因为它们必须迎合离散事件。在 Conal Elliott最初的设想中,行为是时间的简单连续函数
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 的人来说,行为和事件是一个更好的抽象。
(完全披露:我在 Haskell中实现了一个名为reactive-banana的 FRP 库。)