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

JtR*_*JtR 1149 functional-programming terminology frp reactive-programming

我已经读过关于反应式编程的维基百科文章.我还阅读了关于功能反应式编程的小文章.描述非常抽象.

  1. 功能反应式编程(FRP)在实践中意味着什么?
  2. 反应式编程(与非反应式编程相反)是由什么组成的?

我的背景是命令式/ OO语言,因此可以理解与此范例相关的解释.

Con*_*nal 931

如果你想要了解FRP,你可以从1998年的旧Fran教程开始,该教程有动画插图.对于论文,从功能反应动画开始,然后跟进我的主页上的出版物链接和Haskell维基上的FRP链接上的链接.

就个人而言,我想在解决FRP 如何实施之前考虑一下FRP的含义.(没有规范的代码是一个没有问题的答案,因而"甚至没有错".)所以我没有像Thomas K在另一个答案中所做的那样用表达/实现术语描述FRP(图形,节点,边缘,触发,执行,等等).有许多可能的实现方式,但没有实现FRP说什么.

我确实与劳伦斯G的简单描述产生共鸣,即FRP是关于"随时间变化代表价值的数据类型".传统的命令式编程仅通过状态和突变间接捕获这些动态值.完整的历史(过去,现在,将来)没有一流的代表.此外,由于命令式范式在时间上是离散的,因此只能(间接地)捕获离散演化的值.相比之下,FRP 直接捕获这些不断发展的价值,并且对于不断发展的价值没有任何困难.

FRP也是不同寻常的,因为它并没有与理论上和实用的老鼠的巢穴发生冲突,这种老鼠的巢穴困扰着势在必行的并发.从语义上讲,FRP的并发性是细粒度的,确定的连续的.(我说的是意义,而不是实现.实现可能会或可能不会涉及并发或并行.)语义确定性对于推理非常重要,无论是严谨的还是非正式的.虽然并发性为命令式编程增加了极大的复杂性(由于非确定性交错),但它在FRP中毫不费力.

那么,什么是FRP?你可以自己发明它.从这些想法开始:

  • 动态/演化值(即,"随时间变化"的值)本身就是一等值.您可以定义它们并将它们组合,将它们传入和传出函数.我称这些事为"行为".

  • 行为是由一些基元构建的,例如常量(静态)行为和时间(如时钟),然后是顺序和并行组合. 通过应用n元函数(在静态值上),"逐点",即随时间连续地应用n个行为.

  • 为了解释离散现象,有另一种类型(系列)的"事件",每个事件都有一个流(有限或无限)的出现.每次出现都有相关的时间和价值.

  • 要想出可以构建所有行为和事件的组成词汇,请参考一些示例.保持解构为更一般/更简单的部分.

  • 因此,你知道自己处于坚实的基础,使用指称语义技术给整个模型一个组合基础,这意味着(a)每种类型都有相应的简单和精确的"意义"数学类型,并且( b)每个原语和运算符具有作为组成部分含义的函数的简单和精确的含义. 永远不要将实施考虑因素混合到您的探索过程中.如果这个描述对你来说是胡言乱语,请参考(a)具有类型态射的指称设计,(b)推挽功能反应式编程(忽略实现位),以及(c)Denotational Semantics Haskell wikibooks页面.请注意,指称语义有两个部分,来自它的两位创始人Christopher Strachey和Dana Scott:更容易和更有用的Strachey部分以及更难和更少用途(用于软件设计)Scott部分.

如果你坚持这些原则,我希望你会在FRP的精神上获得或多或少的东西.

我从哪里得到这些原则?在软件设计中,我总是问同样的问题:"它是什么意思?".表达语义给了我一个精确的框架来解决这个问题,并且符合我的美学(不同于操作或公理语义,这两者都让我不满意).所以我问自己什么是行为?我很快意识到,命令式计算的暂时离散性质是对特定机器风格的适应性,而不是对行为本身的自然描述.我能想到的最简单的行为描述就是"(连续)时间的功能",这就是我的模型.令人欣喜的是,这个模型轻松而优雅地处理连续,确定的并发.

正确有效地实施这个模型是一个相当大的挑战,但这是另一个故事.

  • @Conal:你清楚地知道你在谈论什么,但是你的语言假设我拥有计算数学博士学位,但我没有.我有系统工程的背景和20多年的计算机和编程语言经验,但我觉得你的回答让我感到困惑.我挑战你用英文重新发布你的回复;-) (212认同)
  • 我已经意识到功能反应式编程.这似乎与我自己的研究(交互式统计图形)有关,我相信很多想法对我的工作都有帮助.但是,我发现很难超越这种语言 - 我是否必须真正了解"指称语义"和"类型类型态射"以了解发生了什么?一般观众对该主题的介绍将非常有用. (78认同)
  • @ minplay.dk:你的评论并没有给我很多关于你不理解的内容,我不愿意猜测你正在寻找什么特定的英语子集.但是,我邀请你具体说明我上面解释的哪些方面你正在绊倒,以便我和其他人可以帮助你.例如,是否有您想要定义的特定单词或您想要添加引用的概念?我真的很喜欢提高写作的清晰度和可访问性 - 而不会让人失望. (50认同)
  • "确定性"/"确定性"意味着有一个明确定义的正确值.相比之下,几乎所有形式的命令式并发都可以提供不同的答案,具体取决于调度程序或者您是否正在查看,它们甚至可能会死锁."语义"(更具体地说是"指称")指的是表达或表示的值("表示"),与"操作"相反(如何计算答案或者消耗多少空间和/或时间)一种机器). (27认同)
  • 我同意@ mindplay.dk虽然我不能吹嘘自己已经在这个领域呆了很长时间.虽然看起来你知道你在说什么,但它并没有给我一个快速,简洁和简单的理解这是什么,因为我已经被宠坏了足以期待SO.这个答案主要是让我在没有真正回答第一个问题的情况下提出了大量新问题.我希望分享在这个领域仍然相对无知的经验可以让你深入了解你真正需要的简单和简洁.我来自与OP相似的背景,顺便说一句. (18认同)
  • "组合"意味着构成的东西,即构造的构造块样式,这是我们如何使用数字,使用算术,幂/根,对数,触发等."词汇"是原子术语的集合和组合运算符/功能. (11认同)
  • FRP是将程序行为表达为将时间值映射到输出值(无论是图形,音乐还是有什么)的数学函数的概念.最简单的例子:一个非常基本的视频播放器是一个程序,可以表示为一个函数,将毫秒映射到应该在MILLISECOND上显示的图片上.例如,在Java语法中类似于public Picture frameForTime(float millisOffsetFromStartOfVideo){//从视频文件中多次解码...返回帧; 但是这些功能必须没有副作用. (10认同)
  • 这是对Conal最后一段的"愚蠢"解释:Conal喜欢声明式编程风格 - 这是基于'含义'而不是指令序列定义语言语法的结果.命令式编程适合于指导例如冯·诺依曼架构(在CPU和RAM之间有明显的区别),而不是描述我们真正关心的VALUES中的变化(让我们称之为"函数").即如果你问一个功能程序员和一个命令式程序员什么是"变量",你会得到完全不同的答案. (9认同)
  • 我认为有许多不熟悉的术语,这些术语将受益于简要解释.例如,确定性和语义决定性在并发性方面意味着什么?成分词汇?如果你对所有这些新术语给出了解释性处理,就像你在"指称语义学"中所做的那样,我认为阅读它的意义要小得多. (8认同)
  • 不幸的是,"一般观众"比Conal的答案更难掌握!当某些事情对我来说不直观时,它*可能意味着我需要更加努力工作,而不是责怪作者特别不能满足我的要求. (7认同)
  • 当然,如果你有足够的兴趣并有几天的业余时间只投资学习这个东西,它有很好的深度,但它通常更有帮助得到一个简单的答案.如果我对其他细节感到好奇,那么我就谷歌那个问题并且很可能找到另一个SO答案,其中解释了特定的东西,并且就像它们完美地连在一起一样.这就是我认为简单和简短的例子是教学/回答的关键的原因之一.希望这能帮到你. (3认同)
  • @AskeB.我认为这个概念的问题在于它依赖于数学家在那些你不知道其含义的词中精确定义的许多重要思想.因此,为了掌握它,您需要知道(至少大多数)引用的概念.如果你这样做(几乎)琐碎地理解这个概念.如果你不这样做,那么在没有首先了解所引用的概念的情况下,你肯定不会得到本质.就是这样. (3认同)
  • @AskeB.这就是这些先进理念的全部意义所在.你可以在几个小时内理解C背后的想法,但很快就会出现复杂的问题.为了解决这些问题,您需要更高级的概念.这是一个非常先进的,仍然是最先进的研究课题(很少实现,甚至更少的程序使用它). (2认同)
  • @AskeB.我可以问你,你是否已经掌握了多态性和继承(这些都是完全独立的概念),并且它们不依赖于OO和c.那些代表/事物的本质,就像上面的答案一样.像Java,C++,Haskell这样的东西只是想法的实现.并且您不一定必须理解这些概念才能使用这些实现.实施者也不必(遗憾地).我会说很多程序员都没有. (2认同)
  • 是的,不幸的是,FRP程序也必须在真实计算机上执行.因此,在性能或利用程序可用的其他可用硬件架构(例如DSP或GPGPU)方面存在一些挑战,但除此之外,这是特定于实现的. (2认同)

Lau*_*ves 739

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

在保留功能风格的同时获得类似行为的副作用的一种方法是使用功能性反应式编程.这是函数式编程和反应式编程的组合.(您链接的维基百科文章是关于后者的.)

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

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

x = <mouse-x>;
y = <mouse-y>;
Run Code Online (Sandbox Code Playgroud)

在任何时候,x和y都有鼠标的坐标.与非反应式编程不同,我们只需要进行一次此分配,x和y变量将自动保持"最新".这就是为什么反应式编程和函数式编程能够很好地协同工作的原因:反应式编程消除了改变变量的需要,同时仍然让你可以通过变量突变完成许多工作.

如果我们然后基于此进行一些计算,结果值也将是随时间变化的值.例如:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;
Run Code Online (Sandbox Code Playgroud)

在此示例中,minX将始终比鼠标指针的x坐标小16.使用反应感知库,您可以说:

rectangle(minX, minY, maxX, maxY)
Run Code Online (Sandbox Code Playgroud)

鼠标指针周围会绘制一个32x32的盒子,无论它在哪里移动都会跟踪它.

这是关于功能性反应式编程的相当好的论文.

  • >那么反应式编程是一种声明式编程呢?*Functional*reactive编程是函数式编程的一种形式,是一种声明式编程. (31认同)
  • 那么反应式编程是一种声明式编程呢? (25认同)
  • @ user712092不是真的,没有.例如,如果我用你的宏在C中调用`sqrt(x)`,那只计算`sqrt(mouse_x())`并给我一个双精度数.在一个真正的功能反应系统中,`sqrt(x)`将返回一个新的"随着时间的推移".如果你试图使用`#define`来模拟FR系统,你几乎必须发誓变量而不是宏.FR系统通常也只会在需要重新计算时重新计算内容,而使用宏意味着您将不断重新评估所有内容,一直到子表达式. (7认同)
  • "对于许多类型的软件(例如,任何有用户交互的东西),副作用在某种程度上是必要的." 也许只有在实施层面.在纯粹的,懒惰的函数式编程的实现中存在许多副作用,并且范例的成功之一是将许多这些效果保留在编程模型之外.我自己进入功能用户界面表明它们也可以完全编程而没有副作用. (4认同)
  • @tieTYT x永远不会被重新分配/变异.x的值是随时间变化的值序列.另一种看待它的方法是,不是x具有"正常"值,如数字,x的值(概念上)是一个将时间作为参数的函数.(这有点过于简单化了.你​​不能创建时间值来预测鼠标位置之类的未来.) (4认同)
  • @TheIronKnuckle"Elm是一种功能性反应式编程(FRP)语言,可编译为HTML,CSS和JS." http://elm-lang.org/ (4认同)
  • d.drawRectangle(minX,minY,maxX,maxY); 旁白:功能行为与功能图形非常吻合:`rectangle(minX,minY,maxX,maxY)`,这将是一个表达式而不是一个语句. (3认同)
  • 所以就好像你在C中写了`#define x mouse_x()`? (3认同)
  • @TheIronKnuckle:javascript还有[培根](https://github.com/baconjs/bacon.js) (3认同)
  • 我现在已经在FRP上打开了大约24个标签,这是迄今为止我发现的最简洁的解释.你应该考虑更广泛地写这个问题,比如一本书.这是一个热门话题,正如我在对父母帖子的评论中所指出的,所有现有材料都是对该主题的天真观点视而不见. (3认同)
  • 我想知道有多少人发现这个更有用,只有在阅读了接受的答案之后,并没有意识到接受的答案让他们准备好了解这一点. (3认同)

小智 144

一个简单的方法就是想象你的程序是一个电子表格而你所有的变量都是单元格.如果电子表格中的任何单元格发生更改,则引用该单元格的任何单元格也会发生更改.它和FRP一样.现在想象一些单元格自行改变(或者更确切地说,取自外部世界):在GUI情况下,鼠标的位置将是一个很好的例子.

这必然会错过很多.当你实际使用FRP系统时,这个比喻会很快崩溃.例如,通常也会尝试对离散事件进行建模(例如,单击鼠标).我只是把它放在这里,让你知道它是什么样的.

  • 一个非常适合的例子.拥有理论上的东西是很棒的,也许有些人在没有借助基础的例子的情况下得到了它的含义,但我需要从它为我做的事情开始,而不是从抽象的事情开始.我最近才得到的(来自Netflix的Rx演讲!)是RP(或Rx,无论如何),使这些"变化的值"成为第一类并让你推理它们,或编写用它们做事的函数.如果您愿意,可以编写函数来创建电子表格或单元格.它会在值结束(消失)时处理并让您自动清理. (3认同)

use*_*092 131

对我而言,它有两种不同的符号含义=:

  1. 在数学x = sin(t)手段,这x不同的名称sin(t).所以写作x + y是一样的sin(t) + y.功能性反应式编程在这方面就像数学一样:如果你编写x + y,它的计算与它使用时的值t无关.
  2. 在类C编程语言(命令式语言)中,x = sin(t)是一个赋值:它意味着x存储赋值时 sin(t)所取的.

  • 很好的解释.我认为你还可以补充说,FRP意义上的"时间"通常是"外部输入的任何变化".无论何时外力改变FRP的输入,您都会向前移动"时间",并再次重新计算受更改影响的所有内容. (5认同)
  • 在数学中`x = sin(t)`表示`x`是给定`t`的`sin(t)`的值.它不是**'sin(t)`作为函数的另一个名称.否则它将是'x(t)= sin(t)`. (4认同)

Tho*_*yer 71

好的,从背景知识和阅读您指向的维基百科页面,看起来反应式编程就像数据流计算,但具有特定的外部"刺激"触发一组节点触发并执行其计算.

这非常适合于UI设计,例如,触摸用户界面控件(例如,音乐播放应用程序上的音量控制)可能需要更新各种显示项目和音频输出的实际音量.当你修改音量(一个滑块,比方说)时,它对应于修改与有向图中节点相关的值.

具有来自该"音量值"节点的边缘的各种节点将被自动触发,并且任何必要的计算和更新将自然地波及整个应用程序.应用程序"响应"用户刺激.功能反应式编程只是在功能语言中实现这一思想,或者通常在函数式编程范式内实现.

有关"数据流计算"的更多信息,请在维基百科上搜索这两个单词或使用您最喜欢的搜索引擎.一般的想法是这样的:程序是节点的有向图,每个节点执行一些简单的计算.这些节点通过图形链接相互连接,图形链接将某些节点的输出提供给其他节点的输入.

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

节点可以被视为并行触发,但通常它们是串行执行的或者具有有限的并行性(例如,可能有几个线程执行它们).一个着名的例子是曼彻斯特数据流机器,它(IIRC)使用标记数据架构来通过一个或多个执行单元来调度图中节点的执行.数据流计算非常适合于这样的情况,其中异步触发计算产生级联计算比尝试执行由时钟(或时钟)控制更好.

反应性编程引入了这种"级联执行"的想法,并且似乎以类似数据流的方式来考虑该程序,但条件是某些节点被连接到"外部世界",并且当这些感知时触发了执行的级联类似的节点改变了.然后程序执行看起来像复杂的反射弧.该程序在刺激之间可能基本上是无柄的,也可能不是在刺激之间的基本无柄状态.

"非反应性"编程将使用与执行流程和与外部输入的关系的非常不同的视图进行编程.这可能有点主观,因为人们很可能会说任何对外部投入做出反应的事情会对他们做出"反应".但是看一下这个东西的精神,一个以固定间隔轮询事件队列并调度发现给函数(或线程)的任何事件的程序反应性较低(因为它只以固定的间隔参与用户输入).再一次,这就是这里的精神:人们可以想象将具有快速轮询间隔的轮询实现放入一个非常低级别的系统中,并以一种被动方式编程.

  • 你提到了数据流,所以增加了一些价值恕我直言. (3认同)
  • 对我来说,这个答案是最容易理解的,特别是因为使用了"通过应用程序涟漪"和"类似感觉的节点"等自然类似物.大! (3认同)

jhe*_*dus 65

在阅读了很多关于FRP的文章之后,我终于遇到了关于FRP的这篇启发性写作,它最终让我明白了FRP究竟是什么.

我引用Heinrich Apfelmus(反应性香蕉的作者).

功能反应式编程的本质是什么?

一个常见的答案是"FRP就是根据时变函数而不是可变状态来描述系统",这肯定不会错.这是语义观点.但在我看来,更深层次,更令人满意的答案是由以下纯粹的句法标准给出的:

功能反应式编程的本质是在声明时完全指定值的动态行为.

例如,以计数器为例:您有两个标记为"向上"和"向下"的按钮,可用于递增或递减计数器.在命令下,您首先要指定一个初始值,然后在按下按钮时更改它; 这样的事情:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)
Run Code Online (Sandbox Code Playgroud)

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

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)
Run Code Online (Sandbox Code Playgroud)

每当你想要理解计数器的动态时,你只需要看它的定义.可能发生的一切都会出现在右侧.这与后续声明可以改变先前声明的值的动态行为的命令式方法形成鲜明对比.

所以,根据我的理解,FRP程序是一组方程式: 在此输入图像描述

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

f取决于t如此,这包含了模拟外部刺激的可能性

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

FRP库负责处理进度时间,换句话说,考虑jj+1.

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

编辑:

在最初答案之后大约2年,最近我得出的结论是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)无效并重新计算.

  • 我和你在一起,直到你使用*discrete*方程式.FRP的创始理念是*连续时间*,其中没有"j + 1".相反,想想连续时间的功能.正如Newton,Leibniz和其他人向我们展示的那样,使用积分和ODE系统来区分这些函数通常非常方便(并且在字面意义上"自然").否则,你将描述一个近似算法(和一个差的算法)而不是事物本身. (4认同)

scv*_*lex 29

论文Conal Elliott 简单有效的功能反应(直接PDF,233 KB)是一个相当不错的介绍.相应的库也可以使用.

该论文现已被另一篇论文" 推拉式功能反应式编程"直接取代(直接PDF,286 KB).


tld*_*ldr 29

免责声明:我的答案是在rx.js的背景下 - 一个用于Javascript的"反应式编程"库.

在函数式编程中,您不是迭代集合中的每个项目,而是将更高阶函数(HoF)应用于集合本身.因此,FRP背后的想法是,不是处理每个单独的事件,而是创建事件流(使用可观察的*实现)并将HoF应用于该事件.通过这种方式,您可以将系统可视化为将发布者与订阅者连接起来的数据管道.

使用observable的主要优点是:
i)它从您的代码中抽象出状态,例如,如果您希望事件处理程序仅针对每个'n'事件被触发,或者在第一个'n'事件之后停止触发,或者只在第一个'n'事件后开始触发,你可以只使用HoFs(filter,takeUntil,skip),而不是设置,更新和检查计数器.
ii)它改进了代码局部性 - 如果你有5个不同的事件处理程序改变组件的状态,你可以合并它们的observable并在合并的observable上定义一个事件处理程序,有效地将5个事件处理程序组合成1.这使得它非常很容易推断整个系统中的哪些事件会影响组件,因为它们都存在于单个处理程序中.

  • Observable是Iterable的双重性.

Iterable是一个懒惰消耗的序列 - 每当它想要使用它时,每个项都由迭代器拉动,因此枚举由消费者驱动.

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

  • _"因此,FRP背后的想法是,不是处理每个单独的事件,而是创建一个事件流(用可观察的*实现)并将HoF应用于那个."_我可能会弄错,但我相信这实际上不是FRP而是相对于Observer设计模式的一个很好的抽象,允许通过HoF进行功能操作(这很棒!),同时仍然打算用于命令式代码.讨论该主题 - http://lambda-the-ultimate.org/node/4982 (2认同)

Dan*_*oss 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()
Run Code Online (Sandbox Code Playgroud)

简而言之:如果每个组件都可以像数字一样对待,那么整个系统就可以像数学方程一样对待,对吧?


Cur*_*son 14

Paul Hudak的书" The Haskell School of Expression"不仅是对Haskell的精细介绍,而且还花费了相当多的时间在FRP上.如果您是FRP的初学者,我强烈建议您了解FRP的工作原理.

这本书的新改写(2011年发布,2014年更新),哈斯克尔音乐学院也是如此.


小智 10

根据之前的答案,从数学上看,我们只是在更高的顺序思考.我们不考虑具有类型X的值x,而是考虑函数x:TX,其中T是时间的类型,无论是自然数,整数还是连续体.现在,当我们在编程语言中写y:= x + 1时,我们实际上意味着方程y(t)= x(t)+ 1.


emp*_*orz 9

如上所述,行为类似于电子表格.通常基于事件驱动的框架.

与所有"范式"一样,它的新颖性值得商榷.

根据我对演员的分布式流网络的经验,它很容易成为整个节点网络中状态一致性的一般问题的牺牲品,即你最终会在奇怪的循环中产生大量的振荡和陷阱.

这很难避免,因为一些语义意味着参考循环或广播,并且当演员网络在一些不可预测的状态上收敛(或不收敛)时可能非常混乱.

同样,尽管有明确的边缘,但可能无法达到某些状态,因为全球状态偏离了解决方案.2 + 2可能会或可能不会变为4取决于2的2变为2,以及他们是否保持这种状态.电子表格具有同步时钟和循环检测.分布式演员通常不会.

一切都很好玩:).


Dan*_*lan 8

我在关于FRP的Clojure subreddit上发现了这个很棒的视频.即使你不了解Clojure,也很容易理解.

以下是视频:http://www.youtube.com/watch?v = ket0K1RXU4

这里的视频指的是在下半场来源:https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs


Gre*_*ant 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