什么是(功能性)反应式编程?


1148

我已经阅读了有关反应式编程的Wikipedia文章。我还阅读了有关函数式反应式编程的小文章。描述非常抽象。

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

我的背景是使用命令式/ OO语言的,因此希望了解与该范例有关的解释。


159
这是一个充满想象力和讲故事能力的家伙。paulstovell.com/reactive-programming
melaos 2010年

39
真的有人需要为我们这里所有的自动编写一个“功能性傻瓜编程”。我发现的所有资源,甚至是榆木,似乎都假设您在过去五年中获得了CS硕士学位。那些了解FRP的人似乎已经完全丧失了从幼稚的角度看问题的能力,这对教学,培训和传福音至关重要。
TechZen 2014年

26
FRP的另一个出色介绍:我的同事André 缺少您对反应式编程的介绍
Jonik

5
我见过的最好的游戏之一,基于示例:gist.github.com/staltz/868e7e9bc2a7b8c1f754
Razmig 2014年

2
我发现电子表格的类比对初次印象非常有用(请参阅Bob的答案:stackoverflow.com/a/1033066/1593924)。电子表格单元格会对其他单元格(拉)中的更改做出反应,但不会伸出手来更改其他单元格(不进行推送)。最终结果是您可以更改一个单元格,而数以百万计的其他单元格则可以“独立”更新其自己的显示。
乔恩·库姆斯

Answers:


931

如果您想对FRP有所了解,可以从1998年的Fran教程开始,该教程带有动画插图。对于论文,请从功能反应动画开始,然后关注我的主页和FRP上出版物链接上的链接 Haskell Wiki上的链接。

就个人而言,我想考虑一下FRP的含义在解决如何实施之前先。(没有规范的代码就是没有问题的答案,因此“甚至没有错”。)因此,我不会像Thomas K在另一个答案(图形,节点,边,触发,执行,等等)。有许多可能的实现方式,但是没有实现说明FRP 什么。

我确实对Laurence G的简单描述感到共鸣,即FRP是关于“表示“随着时间的推移”值的数据类型”的。传统的命令式编程只能通过状态和突变间接地捕获这些动态值。完整的历史记录(过去,现在,将来)没有一流的表示。此外,由于命令式范式在时间上是离散的,因此只能(间接)捕获离散的演化值。相比之下,FRP 直接捕获这些不断变化的值连续不断变化的值没有困难。

FRP也是不寻常的,因为它是并发的,不会与困扰命令式并发的理论和实用大鼠的巢相抵触。从语义上讲,FRP的并发是细粒度的确定的,并且连续的。(我在说的是含义,而不是实现。实现可能涉及或不涉及并发性或并行性。)语义确定性对于严格和非正式的推理非常重要。尽管并发给命令式编程增加了极大的复杂性(由于非确定性交织),但在FRP中却毫不费力。

那么,什么是FRP?您可能自己发明了它。从这些想法开始:

  • 动态/演进值(即“随时间变化的值”)本身就是一等值。您可以定义它们并组合它们,将它们传递到函数中或从函数中传递出去。我称这些事情为“行为”。

  • 行为是由一些原语建立的,例如恒定(静态)行为和时间(例如时钟),然后采用顺序和并行组合。 通过应用n元函数(按静态值)“逐点”(即随时间连续)来组合n个行为。

  • 为了解决离散现象,请使用另一类(事件)“事件”,每个事件都有(有限或无限)事件流。每次出现都有关联的时间和值。

  • 要想出组成所有行为和事件所依据的组成词汇,请参考一些示例。保持解构为更通用/更简单的片段。

  • 为了使您了解自己的立场,使用指称语义技术为整个模型提供组成基础,这意味着(a)每种类型都具有对应的简单而精确的“含义”数学类型,并且( b)每个原语和运算符都具有简单而精确的含义,这取决于成分的含义。 永远不要将实施注意事项混入您的探索过程中。如果您对本说明不满意,请咨询(a)具有类型类态的词义设计,(b)推挽功能反应式编程(忽略实现位)和(c)词义语义 Haskell Wikibooks页面。请注意,指称语义有两个部分,分别来自于两个创始人Christopher Strachey和Dana Scott:Strachey更加容易和有用,而对于软件设计,Scott则更加困难和不那么有用。

如果您遵循这些原则,那么我希望您会从FRP的精神中得到或多或少的东西。

我从哪里得到这些原则?在软件设计中,我总是问同样的问题:“这是什么意思?”。指称语义为我提供了一个精确的框架,可以解决这个问题,并且符合我的审美观(不同于操作性或公理性语义,这两者都使我不满意)。所以我问自己什么是行为?我很快意识到,命令式计算在时间上的离散性可以适应特定类型的机器,而不是对行为本身的自然描述。我能想到的最简单的行为描述就是“(连续)时间的函数”,这就是我的模型。令人高兴的是,此模型轻松而优雅地处理了连续的确定性并发。

正确有效地实现此模型是一个很大的挑战,但这是另一回事了。


78
我知道函数式反应式编程。这似乎与我自己的研究(在交互式统计图形中)有关,并且我相信许多想法对我的工作都会有所帮助。但是,我发现很难超越该语言-我是否必须真正学习“名词性语义”和“类型类态素”以了解正在发生的事情?一般读者对该主题的介绍将非常有用。
hadley

212
@Conal:您清楚地知道您在说什么,但是您的语言假设我拥有计算数学博士学位,而我没有。我确实具有系统工程方面的背景,并且在计算机和编程语言方面拥有20多年的经验,但我仍然感到您的回应让我感到困惑。我要求您以英语重新发送您的回复;-)
mindplay.dk 2011年

50
@ minplay.dk:您的言论并不能给我太多关于您不了解的内容的信息,并且我不愿对您要寻找的特定英语子集做出疯狂的猜测。但是,我请您具体说一下我的解释的哪些方面,以便您和我和其他人帮助您。例如,是否添加了您想要定义的特定单词或想要添加参考的概念?我真的很喜欢提高我的写作的清晰度和可访问性,而不会愚蠢。
康那,

27
“确定性” /“确定性”表示只有一个明确定义的正确值。相比之下,几乎所有形式的命令式并发都可以给出不同的答案,具体取决于调度程序或您是否在寻找,它们甚至可能陷入僵局。“语义”(更具体地说是“名词性”)是指表达式或表示形式的值(“符号”),与“可操作”(如何计算答案或通过什么消耗多少空间和/或时间)相反种机器)。
Conal 2012年

18
我同意@ mindplay.dk,尽管我不能吹牛在该领域工作很长时间。即使您似乎知道您在说什么,也并不能让我快速,简短,简单地了解这是什么,因为我很受宠若惊。这个答案主要是使我提出了很多新问题,而没有真正回答我的第一个问题。我希望分享在该领域仍然相对无知的经验,可以使您深刻了解自己真正需要做到的简单和简短。我来自与OP类似的背景,顺便说一句。
Aske B.

739

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

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

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

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

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的框,并随其移动而对其进行跟踪。

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


25
那么,反应式编程是声明式编程的一种形式吗?
troelskn

31
>那么,反应式编程是声明式编程的一种形式吗? 函数式反应式编程是函数式编程的一种形式,它是声明式编程的一种形式。
康纳尔,

7
@ user712092不是,不是。例如,如果我sqrt(x)用您的宏用C 调用,那只会计算sqrt(mouse_x())并给我加倍。在真正的功能性电抗系统中,sqrt(x)将返回新的“两倍的时间”。如果要尝试使用FR系统进行仿真,则#define几乎必须使用变量来支持宏。FR系统通常也只在需要重新计算内容时才重新计算内容,而使用宏则意味着您将不断重新评估所有内容,一直到子表达式。
劳伦斯·贡萨尔维斯

4
“对于许多类型的软件(例如,任何具有用户交互作用的软件),都需要某种程度的副作用。” 也许仅在实施级别。在执行纯净的,懒惰的函数式编程时会产生很多副作用,该范例的成功之一就是将其中的许多影响排除在编程模型之外。我对功能用户界面的尝试表明,它们也可以完全编程而没有副作用。
Conal 2012年

4
@tieTYT x永远不会重新分配/更改。x的值是随时间变化的值序列。另一种看待它的方法是,x的值(从概念上来说)不是一个像数字一样的“正常”值,而是一个以时间为参数的函数。(这有点过分简化。您无法创建时间值来预测诸如鼠标位置之类的东西的未来。)
Laurence Gonsalves

144

初步了解其外观的一种简单方法是,将程序想象成一个电子表格,而所有变量都是单元格。如果电子表格中的任何单元格发生更改,那么引用该单元格的任何单元格也会发生更改。FRP就是一样。现在想象一下,某些单元格会自行改变(或者更确切地说,是从外部环境获取):在GUI情况下,鼠标的位置将是一个很好的例子。

这肯定会遗漏很多东西。当您实际使用FRP系统时,隐喻分解得很快。一方面,通常也尝试对离散事件进行建模(例如,单击鼠标)。我只是将其放在此处以让您了解它的样子。


3
一个非常合适的例子。拥有理论上的东西真是太好了,也许有些人不用求助于示例就可以理解它的含义,但是我需要从它为我做的事情开始,而不是从抽象的角度开始。我最近才(从Netflix的Rx谈话中得到的)是RP(或Rx,无论如何),使这些“不断变化的价值”成为一流的,并让您对它们进行推理,或编写可对它们进行处理的函数。如果需要,编写函数以创建电子表格或单元格。并且它处理值何时结束(消失)并让您自动清理。
约翰

此示例强调了事件驱动的编程和响应式方法之间的区别,在事件驱动式编程中,您只需声明依赖项即可使用智能路由。
金耶洛姆

131

对我来说,符号有2种不同的含义=

  1. 在数学上x = sin(t)意味着x不同的名称sin(t)。所以写作和写作x + y是一回事sin(t) + y。函数式反应式编程在这方面就像数学:如果您编写x + y,它将使用使用时的值进行计算t
  2. 在类似C的编程语言(命令式语言)中,x = sin(t)是一个赋值:它表示x存储的 sin(t)在分配的时间服用。

5
很好的解释。我想您还可以补充一点,就FRP而言,“时间”通常是“外部输入的任何更改”。每当外力改变FRP的输入时,您就将“时间”向前移动,并再次重新计算受该变化影响的所有内容。
Didier A.

4
在数学中,x = sin(t)均值xsin(t)给定的值t。它与as函数没有不同的名称sin(t)。否则的话x(t) = sin(t)
德米特里·扎伊采夫

+ Dmitri Zaitsev等于符号在数学上有多种含义。其中之一是,每当您看到左侧时,都可以将其与右侧互换。例如2 + 3 = 5a**2 + b**2 = c**2
2016年

71

好吧,从背景知识和阅读您所指向的Wikipedia页面来看,似乎反应式编程类似于数据流计算,但是具有特定的外部“刺激”,触发一组节点触发并执行其计算。

例如,这非常适合UI设计,其中触摸用户界面控件(例如,音乐播放应用程序上的音量控件)可能需要更新各种显示项和音频输出的实际音量。当您修改体积(例如,滑块)时,将相应于修改与有向图中的节点关联的值。

具有从该“体积值”节点开始的边缘的各个节点将被自动触发,并且任何必要的计算和更新自然会在整个应用程序中波动。该应用程序对用户刺激做出“反应”。函数式反应式编程仅是在功能语言中或通常在函数式编程范式中实现此思想。

有关“数据流计算”的更多信息,请在Wikipedia上或使用您喜欢的搜索引擎搜索这两个单词。总体思路是这样的:程序是节点的有向图,每个节点都执行一些简单的计算。这些节点通过图形链接相互连接,这些链接将某些节点的输出提供给其他节点的输入。

当节点触发或执行其计算时,连接到其输出的节点将其相应的输入“触发”或“标记”。具有所有触发/标记/可用输入的节点将自动触发。该图可能是隐式的还是显式的,具体取决于反应式编程的实现方式。

可以将节点视为并行触发,但是通常它们是串行执行的或并行性有限(例如,可能有几个线程执行它们)。曼彻斯特数据流机器Manchester Dataflow Machine)是一个著名的例子,该机器(IIRC)使用标记的数据体系结构通过一个或多个执行单元来调度图中的节点执行。数据流计算非常适合以下情况,在这些情况下,异步触发计算会引起计算级联,而不是尝试让执行受时钟(或多个时钟)支配,效果更好。

响应式编程引入了这种“执行级联”的想法,似乎以一种类似于数据流的方式来思考该程序,但是附带条件是某些节点挂接到“外部世界”,并且当这些感觉到时触发执行级联类节点发生变化。这样,程序执行看起来就类似于复杂的反射弧。该程序在刺激之间可能基本不固执,或者可能在刺激之间基本陷入固执状态。

“非反应式”编程将是一种对执行流程及其与外部输入的关系有截然不同的看法的编程。这可能有点主观,因为人们可能会倾向于说任何响应外部输入的“反应”给他们。但是从事物的本质来看,一个程序以固定的时间间隔轮询事件队列并将发现的事件分派给函数(或线程)的程序反应性较差(因为它仅以固定的时间间隔参与用户输入)。再次,这是这里的实质:可以想象将具有快速轮询间隔的轮询实现以非常低的级别放入系统中,并在其之上以响应方式进行编程。


1
好的,上面有一些很好的答案。我应该删除我的帖子吗?如果我看到两三个人说它没有添加任何东西,除非它的有用数量增加,否则我将其删除。除非它增加了一些价值,否则将其留在这里毫无意义。
Thomas Kammeyer 09年

3
您已经提到了数据流,所以增加了一些恕我直言的价值。
Rainer Joswig 2009年

看来,这就是QML的意思;)
mlvljr

3
对我来说,这个答案是最容易理解的,尤其是因为使用了类似“通过应用程序波纹”和“类似感官的节点”之类的自然类似物。大!
阿克塞利·帕伦(AkseliPalén)2015年

1
不幸的是,Manchester Dataflow Machine链接已死。
Pac0

65

阅读FRP我很多网页后,终于碰上了这个启发写关于FRP,它终于让我明白了什么FRP真的是一回事。

我在下面引用Heinrich Apfelmus(活性香蕉的作者)。

函数式反应式编程的本质是什么?

常见的答案是“ FRP就是要用时变功能而不是可变状态来描述系统”,这肯定不是错误的。这是语义观点。但我认为,以下纯语法标准给出了更深入,更令人满意的答案:

函数式反应式编程的本质是在声明时完全指定值的动态行为。

例如,以计数器为例:您有两个标记为“ Up”和“ Down”的按钮,可用于增加或减少计数器。必须首先指定一个初始值,然后每当按下一个按钮时就对其进行更改。像这样的东西:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)

关键是在声明时,仅指定计数器的初始值。计数器的动态行为在程序的其余部分中是隐含的。相反,函数式反应式编程在声明时指定了整个动态行为,如下所示:

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)

每当您想了解计数器的动态时,只需查看其定义即可。可能发生的所有事情都将出现在右侧。这与命令式方法大不相同,在命令式方法中,后续声明可以更改先前声明的值的动态行为。

因此,以我的理解,FRP程序是一组方程式: 在此处输入图片说明

j 是离散的:1,2,3,4 ...

f取决于,t因此这结合了对外部刺激进行建模的可能性

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

FRP库负责处理时间,换句话说,要占用j时间j+1

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

编辑:

在最初回答大约两年之后,最近我得出结论,即FRP实施还有另一个重要方面。他们需要(通常是)解决一个重要的实际问题:缓存无效

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

此外,x_i可以更改的-s可以进行增量更新。例如,让我们考虑一个地图操作f=g.map(_+1)在Scala中,其中fgListInts。这里f对应于x_i(t_j)gx_j(t_j)。现在,如果我在前面添加一个元素,g那么map对中的所有元素进行操作将很浪费g。一些FRP实现(例如reflex- frp )旨在解决此问题。此问题也称为增量计算。

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


4
在您使用离散方程之前,我一直在那里。FRP的创始理念是持续时间,没有“ j+1”。相反,请考虑连续时间的功能。正如牛顿,莱布尼兹和其他人向我们展示的那样,使用ODE的积分和系统来区别地描述这些函数通常非常方便(从字面上看是“自然的”)。否则,您将描述一种近似算法(而不是一种不良算法),而不是事物本身。
康纳尔,

HTML模板和布局约束语言来信似乎表达了FRP的元素。

@Conal这使我想知道FRP与ODE有何不同。它们有何不同?
jhegedus

@jhegedus在这种集成中(可能是递归的,即ODE)提供了FRP的组成部分之一,而不是全部。FRP词汇表中的每个元素(包括但不限于集成)都是根据连续时间来精确解释的。这种解释有帮助吗?
康那,


29

免责声明:我的回答是在rx.js的上下文中进行的-rx.js是Javascript的“反应式编程”库。

在函数式编程中,您无需遍历集合的每个项目,而是将高阶函数(HoF)应用于集合本身。因此,FRP背后的想法是,与其处理每个单独的事件,不如创建一个事件流(以observable *实现)并将HoF应用于该事件流。这样,您可以将系统可视化为将发布者连接到订阅者的数据管道。

使用可观察对象的主要优点是:
i)从代码中抽象出状态,例如,如果您希望仅对每个第n个事件触发事件处理程序,或者在第一个n个事件后停止触发,或仅在第一个“ n”个事件之后才开始触发,您可以只使用HoF(分别是filter,takeUntil,skip),而不用设置,更新和检查计数器。
ii)它提高了代码的局部性-如果您有5个不同的事件处理程序来更改组件的状态,则可以合并其可观察对象,并在合并的可观察对象上定义单个事件处理程序,从而将5个事件处理程序有效地组合为1。由于整个事件都存在于单个处理程序中,因此很容易推断整个系统中的哪些事件会影响组件。

  • 一个Observable是Iterable的对偶。

一个Iterable是一个延迟消费的序列-每当它想要使用它时,它都会将其拉出,因此枚举由消费者驱动。

可观察的是一个延迟生成的序列-每当将每个项目添加到序列中时,每个项目都会被推送到观察者,因此枚举由生产者驱动。


1
非常感谢您对可观察对象的简单定义以及它与可迭代对象的区别。我认为将一个复杂的概念与其众所周知的双重概念进行比较通常会很有帮助,从而获得真正的了解。

2
“因此,FRP背后的想法是,不处理每个事件,而是创建事件流(使用可观察的*实现),然后将HoF应用于事件流。” 我可能会误会,但我认为这实际上不是FRP,而是对Observer设计模式的一种很好的抽象,它允许通过HoF进行功能性操作(太好了!),但仍打算与命令式代码一起使用。关于该主题的讨论-lambda-the-ultimate.org/node/4982
nqe

18

杜德,这真是个绝妙的主意!为什么我不早在1998年就知道了?无论如何,这是我对Fran教程的解释。欢迎提出建议,我正在考虑基于此启动游戏引擎。

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

简而言之:如果每个组件都可以被视为一个数字,那么整个系统可以被视为一个数学方程式,对吗?


1
这有点晚了,但是无论如何... Frag是一款使用FRP的游戏
2013年


10

根据先前的答案,似乎在数学上,我们只是以更高的顺序进行思考。我们不用考虑具有类型X的值x,而是考虑函数xTX,其中T是时间的类型,可以是自然数,整数或连续体。现在,当我们用编程语言编写y:= x + 1时,实际上是指方程yt)= xt)+ 1。


9

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

与所有“范例”一样,其新颖性值得商de。

根据我对参与者的分布式流网络的经验,它很容易成为节点网络上状态一致性的一般问题的牺牲品,即最终会导致大量振荡和陷入怪圈。

这是很难避免的,因为某些语义暗示了引用循环或广播,并且随着参与者网络在某个不可预测的状态上收敛(或不收敛)而变得非常混乱。

类似地,尽管具有明确定义的边缘,但可能无法到达某些状态,因为全局状态会偏离解决方案。2 + 2可能等于或不等于4,具体取决于2变成2的时间以及它们是否保持这种状态。电子表格具有同步时钟和循环检测。分散的演员通常不会。

一切都很好玩:)。



7

到目前为止,Andre Staltz的这篇文章是迄今为止最好,最清晰的解释。

文章中的一些引文:

反应式编程是使用异步数据流进行编程。

最重要的是,您将获得令人惊叹的功能工具箱,以组合,创建和过滤任何这些流。

这是本文一部分的奇妙图表的示例:

点击事件流程图


5

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

在代码中,这意味着功能纯净和声明式编程。

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

在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


这正是我对声明式编程的感觉,而您只是比我更好地描述了这个想法。
neevek

2

关于循环反应编程的简短说明出现在Cyclejs-循环反应编程上,它使用简单直观的示例。

[模块/组件/对象] 是反应性的,它完全负责通过对外部事件做出反应来管理自己的状态。

这种方法的好处是什么?这是控制反转,主要是因为[module / Component / object]负责自身,使用私有方法相对于公共方法改进了封装。

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


0

查看Rx,.NET的Reactive Extensions。他们指出,使用IEnumerable,您基本上是从流中“拉出”。通过IQueryable / IEnumerable进行的Linq查询是集合操作,可将结果从集合中“吸出”。但是,使用相同的IObservable运算符,您可以编写“反应”的Linq查询。

例如,您可以编写一个Linq查询,例如(从MyObservableSetOfMouseMovements中的m,其中mX <100和mY <100选择新Point(mX,mY))。

有了Rx扩展,就是这样:您具有UI代码,可以响应传入的鼠标移动流并在处于100,100框时进行绘制...


0

FRP是功能编程(基于一切都是功能的编程范式)和反应式编程(基于一切都是流的概念(观察者和可观察的哲学)的构建)的组合。它应该是世界上最好的。

查阅Andre Staltz关于反应式编程的文章。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.