它有时被称为“功能反应式编程”,但这是用词不当。 ReactiveX 可能是功能性的,它可能是反应性的,但“功能反应性编程”是一种不同的动物。一个主要的不同点是功能性反应式编程对随时间连续变化的值进行操作,而 ReactiveX 对随时间发射的离散值进行操作。
同时,从维基百科的功能反应编程页面开始,ReactiveX 列在“实现”部分中:
实施[6]
-
cellx,Ultra-fast 执行 javascript 的反应性
-
Elm,编译为 HTML,CSS 和 JavaScript 的 FRP 语言
-
Ruby 中的 Frappuccino FRP 实现
-
Flapjax,behavior/event JavaScript 中的 FRP 实现
-
Reactive.jl,朱莉娅的 FRP 实施
-
ReactiveX,多种语言的 FRP 实现,包括 Java,JavaScript,Python,Swift 等等
-
reactive-banana Haskell 中的 FRP 实现
-
ReactiveCocoa FRP 在 Swift 和 Objective-C 中实现
-
ReactiveKit FRP 在纯 Swift 中实现
-
Haskell 中的 Reflex FRP 实现
-
Scala.Rx Scala 中的 FRP 实现(和 Scala.js)
-
Codium,C,Haskell 中的 Sodium,FRP 实现(不推荐使用[7]),Java,> Rust 和 Scala
-
Haskell 中的 Yampa FRP 实现
我非常了解 ReactiveX 的作用,并对“反应式编程”和“功能反应式编程”进行了一些研究,但我仍然无法区分它们之间的关系。
事实上,我有点认为维基百科页面用词不当,或者错误地列出了“实现”部分中的示例,因为我知道cellx和ReactiveX(两者都列在例子中)是为了解决完全不同的问题而构建的。
2 回答
这里 reactive-banana 库的作者。
功能反应式编程(FRP)和反应式编程(RP)之间的关键区别在于,前者具有 well-defined 指称语义,通常从类型中获得
而后者没有 well-defined 指称语义。特别是,我所知道的 RX 的所有实现都遇到了合并事件流的问题是 non-deterministic:当流包含同时发生的事件时,有时一个事件在另一个事件之前合并,有时反之亦然。
此外,“FRP 对随时间变化的值进行操作”的说法既微妙不正确,也不是关键区别:
首先,这个陈述最可能的解析是“行为是连续函数
Time -> a
”,这不是真的:行为可以是不连续的,例如它们可以是阶梯函数。真实的是,FRP 中的Time
通常被认为是一个实数,i.e。一连串的价值观。其次,完全可能在时间不连续的情况下使用 FRP。这不是 RP 的关键区别,而是关于您对值的操作是否具有 well-defined 指称语义。
据我了解,从 ReactiveX(又名 RX)的角度来看,根本不可能在同一个“时间”发生两个事件。这些只是内部,按顺序按订阅顺序触发的回调。 RX 没有“管理”时间。
通过纯粹的 FRP 程序员的眼睛,RX 可以表现得相当疯狂。考虑以下 RXJS 代码:
这里
xs
是一个冷可观察区间,可以尽快发射。由于xs.combineLatest(ys, f)
总是首先订阅xs
,然后订阅ys
,你会期望xs.combineLatest(xs, (a,b) => [a,b])
生成[0,0], [1,0], [1,1], [2,1], ...
所以ab[1] > ab[0]
应该总是为假。但是,在我的电脑上,如果我让这段代码运行一段时间,它会在某个时刻结束可能需要一段时间,亲自尝试一下那是因为
xs
是一个冷可观察的:每个订阅interval
将创建一个独立运行的周期性计时器。这些计时器可以并且将在某个时刻以不同的顺序触发(特别是在 multi-threaded 环境中,如.NET)如果我们注释
//share
行,使xs
变热,则序列永远不会完成,因为现在确定性地生成了[0,0], [1,0], [1,1], ... ,[i,i-1],[i,i]...
。这是因为 hot observable 共享一个订阅。在这种情况下,只创建一个计时器。在真正的 FRP 系统中,这种行为将是确定性的。但是,如果您真的要连接到真实 FRP 系统中的不同硬件定时器,您也会获得与 RX 相同的行为,因为这些外部事件将以随机顺序触发,除非两个定时器完全同步