我读过维基百科上关于响应式编程的文章。我还读过一篇关于函数式响应式编程的小文章。这些描述相当抽象。

函数式响应式编程(FRP)在实践中意味着什么? 反应式编程(相对于非反应式编程?)由什么组成?

我的背景是命令式/OO语言,所以与此范例相关的解释将受到赞赏。


当前回答

FRP是函数式编程(编程范式建立在一切都是函数的思想上)和响应式编程范式(建立在一切都是流的思想上(观察者和可观察的哲学))的结合。它应该是世界上最好的。

看看Andre Staltz关于响应式编程的文章。

其他回答

它是关于随着时间(或忽略时间)的数学数据转换。

在代码中,这意味着函数的纯洁性和声明性编程。

状态错误是标准命令式范例中的一个大问题。不同的代码位可能在程序执行的不同“时间”改变一些共享状态。这很难处理。

在FRP中,你描述了(就像在声明式编程中一样)数据如何从一种状态转换到另一种状态,以及触发它的是什么。这允许您忽略时间,因为您的函数只是对其输入作出反应,并使用它们的当前值创建一个新值。这意味着状态包含在转换节点的图(或树)中,并且在功能上是纯的。

这大大降低了复杂性和调试时间。

想想数学中的A=B+C和程序中的A=B+C之间的区别。 在数学中,你描述的是一种永不改变的关系。在一个程序中,它说“现在”a是B+C。但是下一个命令可能是b++,在这种情况下A不等于B+C。在数学或声明性编程中,A总是等于B+C,无论你在什么时候问。

因此,通过消除共享状态的复杂性并随时间改变值。你的程序更容易推理。

EventStream是一个EventStream +一些转换函数。

行为是一个EventStream +内存中的某个值。

当事件触发时,通过运行转换函数更新值。这产生的值存储在行为内存中。

行为可以被组合以产生新的行为,这些行为是对N个其他行为的转换。该组合值将在输入事件(行为)触发时重新计算。

由于观察器是无状态的,我们经常需要几个观察器来模拟一个状态机,就像在拖动示例中那样。我们必须保存所有相关观察者都可以访问的状态,比如上面的变量路径。”

引用自-弃用观察者模式 http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

在纯函数式编程中,没有副作用。对于许多类型的软件(例如,任何与用户交互的软件),在某种程度上副作用都是必要的。

在保持函数式风格的同时获得类似副作用的行为的一种方法是使用函数式响应式编程。这是函数式编程和响应式编程的结合。(你链接到的维基百科文章是关于后者的。)

响应式编程背后的基本思想是,有特定的数据类型表示“随时间”的值。涉及这些随时间变化的值的计算本身也具有随时间变化的值。

例如,您可以将鼠标坐标表示为一对随时间变化的整数值。假设我们有这样的东西(这是伪代码):

x = <mouse-x>;
y = <mouse-y>;

在任何时刻,x和y都是鼠标的坐标。与非响应式编程不同,我们只需要进行一次赋值,x和y变量将自动保持“最新”。这就是响应式编程和函数式编程协同工作的原因:响应式编程消除了对变量突变的需要,同时仍然允许您完成许多可以通过变量突变完成的工作。

如果我们在此基础上进行一些计算,得到的值也将是随时间变化的值。例如:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

在这个例子中,minX总是比鼠标指针的x坐标小16。使用响应式感知库,你可以这样说:

rectangle(minX, minY, maxX, maxY)

一个32x32的方框将围绕鼠标指针绘制,并跟踪它的移动位置。

这是一篇关于函数式响应式编程的很好的论文。

就像电子表格一样。通常基于事件驱动框架。

和所有的“范式”一样,它的新颖性是有争议的。

根据我对参与者的分布式流网络的经验,它很容易陷入节点网络状态一致性的普遍问题,即你最终会陷入很多振荡并陷入奇怪的循环中。

这是很难避免的,因为一些语义意味着引用循环或广播,并且当参与者网络收敛(或不收敛)在某些不可预知的状态时,可能会非常混乱。

类似地,尽管具有定义良好的边缘,但可能无法到达某些状态,因为全局状态偏离了解决方案。2+2可能等于4,也可能不等于4,这取决于2是什么时候变成2的,以及它们是否一直是这样。电子表格具有同步时钟和循环检测。分布式参与者通常不会。

一切都很有趣:)。

关于响应式编程的简短而清晰的解释出现在Cyclejs -响应式编程中,它使用了简单和可视化的示例。

一个[模块/组件/对象]是反应性的意味着它是完全负责的 通过对外部事件的反应来管理自己的状态。 这种方法的好处是什么?这就是控制反转, 主要是因为[module/Component/object]对自己负责,使用私有方法来改进封装。

这是一个很好的起点,而不是一个完整的知识来源。从那里你可以跳到更复杂和更深入的文件。

在阅读了许多页关于FRP的文章后,我终于看到了这篇关于FRP的启发性文章,它最终让我明白了FRP的真正含义。

下面我引用海因里希·阿费尔马斯(活性香蕉的作者)的话。

What is the essence of functional reactive programming? A common answer would be that “FRP is all about describing a system in terms of time-varying functions instead of mutable state”, and that would certainly not be wrong. This is the semantic viewpoint. But in my opinion, the deeper, more satisfying answer is given by the following purely syntactic criterion: The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration. For instance, take the example of a counter: you have two buttons labelled “Up” and “Down” which can be used to increment or decrement the counter. Imperatively, you would first specify an initial value and then change it whenever a button is pressed; something like this: counter := 0 -- initial value on buttonUp = (counter := counter + 1) -- change it later on buttonDown = (counter := counter - 1) The point is that at the time of declaration, only the initial value for the counter is specified; the dynamic behavior of counter is implicit in the rest of the program text. In contrast, functional reactive programming specifies the whole dynamic behavior at the time of declaration, like this: counter :: Behavior Int counter = accumulate ($) 0 (fmap (+1) eventUp `union` fmap (subtract 1) eventDown) Whenever you want to understand the dynamics of counter, you only have to look at its definition. Everything that can happen to it will appear on the right-hand side. This is very much in contrast to the imperative approach where subsequent declarations can change the dynamic behavior of previously declared values.

所以,在我的理解中,FRP程序是一组方程:

J是离散的:1,2,3,4…

F依赖于t所以这包含了外部刺激模型的可能性

程序的所有状态都封装在变量x_i中

FRP库考虑了进度时间,换句话说,从j到j+1。

我会在这个视频中更详细地解释这些方程。

编辑:

在最初的回答大约2年后,最近我得出结论,FRP实现还有另一个重要的方面。它们需要(通常也会)解决一个重要的实际问题:缓存失效。

x_i-s的方程描述了一个依赖关系图。当x_i在j时刻发生变化时,并不需要更新j+1时刻的所有其他x_i'值,因此并不需要重新计算所有依赖项,因为有些x_i'可能与x_i无关。

而且,改变的x_i-s可以被增量更新。例如,让我们考虑Scala中的映射操作f=g.map(_+1),其中f和g是int类型的列表。这里f对应于x_i(t_j) g是x_j(t_j)现在,如果我将一个元素前置到g中,那么对g中的所有元素执行映射操作将是浪费的。一些FRP实现(例如reflect - FRP)旨在解决这个问题。这个问题也称为增量计算。

换句话说,FRP中的行为(x_i-s)可以被认为是缓存的计算。如果某些f_i-s确实发生了变化,FRP引擎的任务就是有效地使这些缓存(x_i-s)失效并重新计算。