我读过维基百科上关于响应式编程的文章。我还读过一篇关于函数式响应式编程的小文章。这些描述相当抽象。
函数式响应式编程(FRP)在实践中意味着什么? 反应式编程(相对于非反应式编程?)由什么组成?
我的背景是命令式/OO语言,所以与此范例相关的解释将受到赞赏。
我读过维基百科上关于响应式编程的文章。我还读过一篇关于函数式响应式编程的小文章。这些描述相当抽象。
函数式响应式编程(FRP)在实践中意味着什么? 反应式编程(相对于非反应式编程?)由什么组成?
我的背景是命令式/OO语言,所以与此范例相关的解释将受到赞赏。
当前回答
就像电子表格一样。通常基于事件驱动框架。
和所有的“范式”一样,它的新颖性是有争议的。
根据我对参与者的分布式流网络的经验,它很容易陷入节点网络状态一致性的普遍问题,即你最终会陷入很多振荡并陷入奇怪的循环中。
这是很难避免的,因为一些语义意味着引用循环或广播,并且当参与者网络收敛(或不收敛)在某些不可预知的状态时,可能会非常混乱。
类似地,尽管具有定义良好的边缘,但可能无法到达某些状态,因为全局状态偏离了解决方案。2+2可能等于4,也可能不等于4,这取决于2是什么时候变成2的,以及它们是否一直是这样。电子表格具有同步时钟和循环检测。分布式参与者通常不会。
一切都很有趣:)。
其他回答
好的,从背景知识和阅读你所指向的维基百科页面来看,响应式编程似乎有点像数据流计算,但有特定的外部“刺激”触发一组节点来触发并执行它们的计算。
这非常适合UI设计,例如,触摸用户界面控件(例如,音乐播放应用程序上的音量控制)可能需要更新各种显示项和音频输出的实际音量。当您修改体积(比如一个滑块)时,这将对应于修改有向图中与节点相关的值。
具有“体积值”节点边缘的各种节点将自动被触发,任何必要的计算和更新将自然地贯穿整个应用程序。应用程序对用户刺激“做出反应”。函数式响应式编程只是在函数式语言中实现这一思想,或者通常在函数式编程范式中实现。
有关“数据流计算”的更多信息,请在维基百科或使用您喜欢的搜索引擎上搜索这两个词。总体思想是这样的:程序是一个节点的有向图,每个节点执行一些简单的计算。这些节点通过图链接相互连接,图链接将一些节点的输出提供给其他节点的输入。
当节点触发或执行其计算时,连接到其输出的节点将“触发”或“标记”相应的输入。任何触发/标记/可用所有输入的节点都会自动触发。图可以是隐式的,也可以是显式的,具体取决于响应式编程是如何实现的。
Nodes can be looked at as firing in parallel, but often they are executed serially or with limited parallelism (for example, there may be a few threads executing them). A famous example was the Manchester Dataflow Machine, which (IIRC) used a tagged data architecture to schedule execution of nodes in the graph through one or more execution units. Dataflow computing is fairly well suited to situations in which triggering computations asynchronously giving rise to cascades of computations works better than trying to have execution be governed by a clock (or clocks).
响应式编程引入了这种“执行级联”的思想,似乎以一种类似数据流的方式来考虑程序,但有一个附带条件,即一些节点与“外部世界”挂钩,当这些类似感知的节点发生变化时,执行级联就会被触发。程序的执行看起来就像一个复杂的反射弧。程序在两个刺激之间可能是基本固定的,也可能不是,也可能在两个刺激之间稳定在基本固定的状态。
"non-reactive" programming would be programming with a very different view of the flow of execution and relationship to external inputs. It's likely to be somewhat subjective, since people will likely be tempted to say anything that responds to external inputs "reacts" to them. But looking at the spirit of the thing, a program that polls an event queue at a fixed interval and dispatches any events found to functions (or threads) is less reactive (because it only attends to user input at a fixed interval). Again, it's the spirit of the thing here: one can imagine putting a polling implementation with a fast polling interval into a system at a very low level and program in a reactive fashion on top of it.
它是关于随着时间(或忽略时间)的数学数据转换。
在代码中,这意味着函数的纯洁性和声明性编程。
状态错误是标准命令式范例中的一个大问题。不同的代码位可能在程序执行的不同“时间”改变一些共享状态。这很难处理。
在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的方框将围绕鼠标指针绘制,并跟踪它的移动位置。
这是一篇关于函数式响应式编程的很好的论文。
如果你想感受一下FRP,你可以从1998年的Fran教程开始,它有动画插图。对于论文,从函数反应动画开始,然后在我的主页上的出版物链接和Haskell wiki上的FRP链接上跟踪链接。
就我个人而言,我喜欢在讨论如何实施FRP之前思考它意味着什么。 (没有规范的代码是没有问题的答案,因此“甚至没有错”。) 因此,我没有像Thomas K在另一个答案(图、节点、边、触发、执行等)中那样用表示/实现术语描述FRP。 有许多可能的实现风格,但没有一种实现说明FRP是什么。
I do resonate with Laurence G's simple description that FRP is about "datatypes that represent a value 'over time' ". Conventional imperative programming captures these dynamic values only indirectly, through state and mutations. The complete history (past, present, future) has no first class representation. Moreover, only discretely evolving values can be (indirectly) captured, since the imperative paradigm is temporally discrete. In contrast, FRP captures these evolving values directly and has no difficulty with continuously evolving values.
FRP is also unusual in that it is concurrent without running afoul of the theoretical & pragmatic rats' nest that plagues imperative concurrency. Semantically, FRP's concurrency is fine-grained, determinate, and continuous. (I'm talking about meaning, not implementation. An implementation may or may not involve concurrency or parallelism.) Semantic determinacy is very important for reasoning, both rigorous and informal. While concurrency adds enormous complexity to imperative programming (due to nondeterministic interleaving), it is effortless in FRP.
那么,什么是FRP? 你可以自己发明的。 从这些想法开始:
Dynamic/evolving values (i.e., values "over time") are first class values in themselves. You can define them and combine them, pass them into & out of functions. I called these things "behaviors". Behaviors are built up out of a few primitives, like constant (static) behaviors and time (like a clock), and then with sequential and parallel combination. n behaviors are combined by applying an n-ary function (on static values), "point-wise", i.e., continuously over time. To account for discrete phenomena, have another type (family) of "events", each of which has a stream (finite or infinite) of occurrences. Each occurrence has an associated time and value. To come up with the compositional vocabulary out of which all behaviors and events can be built, play with some examples. Keep deconstructing into pieces that are more general/simple. So that you know you're on solid ground, give the whole model a compositional foundation, using the technique of denotational semantics, which just means that (a) each type has a corresponding simple & precise mathematical type of "meanings", and (b) each primitive and operator has a simple & precise meaning as a function of the meanings of the constituents. Never, ever mix implementation considerations into your exploration process. If this description is gibberish to you, consult (a) Denotational design with type class morphisms, (b) Push-pull functional reactive programming (ignoring the implementation bits), and (c) the Denotational Semantics Haskell wikibooks page. Beware that denotational semantics has two parts, from its two founders Christopher Strachey and Dana Scott: the easier & more useful Strachey part and the harder and less useful (for software design) Scott part.
如果你坚持这些原则,我希望你能得到或多或少符合FRP精神的东西。
Where did I get these principles? In software design, I always ask the same question: "what does it mean?". Denotational semantics gave me a precise framework for this question, and one that fits my aesthetics (unlike operational or axiomatic semantics, both of which leave me unsatisfied). So I asked myself what is behavior? I soon realized that the temporally discrete nature of imperative computation is an accommodation to a particular style of machine, rather than a natural description of behavior itself. The simplest precise description of behavior I can think of is simply "function of (continuous) time", so that's my model. Delightfully, this model handles continuous, deterministic concurrency with ease and grace.
正确有效地实现这个模型是一个相当大的挑战,但那是另一个故事了。
关于响应式编程的简短而清晰的解释出现在Cyclejs -响应式编程中,它使用了简单和可视化的示例。
一个[模块/组件/对象]是反应性的意味着它是完全负责的 通过对外部事件的反应来管理自己的状态。 这种方法的好处是什么?这就是控制反转, 主要是因为[module/Component/object]对自己负责,使用私有方法来改进封装。
这是一个很好的起点,而不是一个完整的知识来源。从那里你可以跳到更复杂和更深入的文件。