我读过维基百科上关于响应式编程的文章。我还读过一篇关于函数式响应式编程的小文章。这些描述相当抽象。
函数式响应式编程(FRP)在实践中意味着什么? 反应式编程(相对于非反应式编程?)由什么组成?
我的背景是命令式/OO语言,所以与此范例相关的解释将受到赞赏。
我读过维基百科上关于响应式编程的文章。我还读过一篇关于函数式响应式编程的小文章。这些描述相当抽象。
函数式响应式编程(FRP)在实践中意味着什么? 反应式编程(相对于非反应式编程?)由什么组成?
我的背景是命令式/OO语言,所以与此范例相关的解释将受到赞赏。
当前回答
有一种简单的方法可以直观地了解它是什么样子的,那就是把你的程序想象成一个电子表格,所有的变量都是单元格。如果电子表格中的任何单元格发生变化,则引用该单元格的任何单元格也会发生变化。玻璃钢也是一样。现在想象一下,一些单元格会自己改变(或者更确切地说,是从外部世界中获取的):在GUI情况下,鼠标的位置就是一个很好的例子。
这必然会错过很多东西。当你实际使用FRP系统时,这个比喻很快就被打破了。首先,通常也会尝试建模离散事件(例如鼠标被点击)。我把这个放在这里只是为了让你们了解它是什么样的。
其他回答
在纯函数式编程中,没有副作用。对于许多类型的软件(例如,任何与用户交互的软件),在某种程度上副作用都是必要的。
在保持函数式风格的同时获得类似副作用的行为的一种方法是使用函数式响应式编程。这是函数式编程和响应式编程的结合。(你链接到的维基百科文章是关于后者的。)
响应式编程背后的基本思想是,有特定的数据类型表示“随时间”的值。涉及这些随时间变化的值的计算本身也具有随时间变化的值。
例如,您可以将鼠标坐标表示为一对随时间变化的整数值。假设我们有这样的东西(这是伪代码):
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的方框将围绕鼠标指针绘制,并跟踪它的移动位置。
这是一篇关于函数式响应式编程的很好的论文。
Conal Elliott的论文《Simply efficient functional reactivity》(直接PDF, 233 KB)是一个相当好的介绍。相应的库也可以工作。
这篇论文现在被另一篇论文取代,推拉函数式反应性编程(直接PDF, 286 KB)。
我在Clojure reddit上找到了一个关于FRP的视频。即使你不懂Clojure,也很容易理解。
这是视频:http://www.youtube.com/watch?v=nket0K1RXU4
这是视频后半段提到的来源:https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs
Paul Hudak的书,The Haskell School of Expression,不仅是对Haskell的很好的介绍,而且还花了相当多的时间在FRP上。如果你是FRP的初学者,我强烈推荐它让你了解FRP是如何工作的。
还有一本看起来像是这本书(2011年出版,2014年更新)的新重写版——哈斯克尔音乐学院。
好的,从背景知识和阅读你所指向的维基百科页面来看,响应式编程似乎有点像数据流计算,但有特定的外部“刺激”触发一组节点来触发并执行它们的计算。
这非常适合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.