【问题标题】:What is (functional) reactive programming?什么是(函数式)反应式编程?
【发布时间】:2010-11-04 22:08:03
【问题描述】:

我已经阅读了reactive programming 上的维基百科文章。我还阅读了functional reactive programming 上的小文章。描述很抽象。

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

我的背景是命令式/OO 语言,因此如果能提供与此范例相关的解释,将不胜感激。

【问题讨论】:

  • 这是一个具有积极想象力和良好讲故事能力的人,他负责整个事情。 paulstovell.com/reactive-programming
  • 真的需要有人为我们这里的自学者写一个“傻瓜函数式反应式编程”。我找到的每一个资源,甚至 Elm,似乎都假设你在过去五年中获得了 CS 硕士学位。那些了解 FRP 的人似乎完全失去了从幼稚的角度看待问题的能力,这对教学、培训和传福音至关重要。
  • 另一个优秀的 FRP 介绍:The introduction to Reactive Programming you've been missing 我的同事 André
  • 我见过的最好的之一,基于示例:gist.github.com/staltz/868e7e9bc2a7b8c1f754
  • 我发现电子表格类比作为第一印象非常有用(请参阅 Bob 的回答:stackoverflow.com/a/1033066/1593924)。电子表格单元格对其他单元格中的更改做出反应(拉动),但不会伸出并更改其他单元格(不推送)。最终结果是您可以更改一个单元格,而无数其他单元格“独立”更新自己的显示。

标签: functional-programming terminology reactive-programming frp


【解决方案1】:

如果你想感受一下 FRP,你可以从 1998 年的旧 Fran tutorial 开始,它有动画插图。对于论文,从Functional Reactive Animation 开始,然后跟进我主页上的出版物链接和Haskell wiki 上的FRP 链接。

就个人而言,我喜欢在讨论 FRP 的实施方式之前考虑它的含义。 (没有规范的代码是没有问题的答案,因此“甚至没有错”。) 因此,我不会像 Thomas K 在另一个答案(图、节点、边、触发、执行等)中那样在表示/实现术语中描述 FRP。 有许多可能的实现样式,但没有一个实现说明 FRP

我确实赞同 Laurence G 的简单描述,即 FRP 是关于“表示‘随时间’变化的值的数据类型”。 传统的命令式编程仅通过状态和突变间接地捕获这些动态值。 完整的历史(过去、现在、未来)没有一流的表现。 此外,只有 离散演变 值可以(间接)捕获,因为命令式范式在时间上是离散的。 相比之下,FRP直接捕获这些不断变化的值,并且连续不断变化的值没有困难。

FRP 的不寻常之处还在于它是并发的,而不会与困扰命令式并发的理论和实用鼠巢相冲突。 从语义上讲,FRP 的并发是细粒度的确定的连续的。 (我说的是意义,而不是实现。实现可能涉及也可能不涉及并发或并行性。) 语义确定性对于推理非常重要,无论是严谨的还是非正式的。 虽然并发给命令式编程增加了巨大的复杂性(由于不确定性交错),但在 FRP 中却毫不费力。

那么,什么是玻璃钢? 你本可以自己发明的。 从这些想法开始:

  • 动态/不断变化的值(即“随时间变化”的值)本身就是一流的值。您可以定义它们并将它们组合起来,将它们传入和传出函数。我把这些东西称为“行为”。

  • 行为由几个原语构成,如恒定(静态)行为和时间(如时钟),然后是顺序和并行组合。 n 行为是通过应用一个 n 元函数(在静态值上)“逐点”组合的,即随着时间的推移连续。

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

  • 要提出可以构建所有行为和事件的组合词汇,请使用一些示例。继续解构为更一般/更简单的部分。

  • 1234563 “含义”,以及 (b) 每个原语和运算符都具有简单而精确的含义,作为成分含义的函数。 永远不要将实施考虑因素混入您的探索过程中。如果此描述对您来说是胡言乱语,请咨询 (a) Denotational design with type class morphisms、(b) Push-pull functional reactive programming(忽略实现位)和 (c) @987654327 @。请注意,指称语义有两个部分,来自其两位创始人 Christopher Strachey 和 Dana Scott:更容易且更有用的 Strachey 部分和更难且不太有用(对于软件设计)的 Scott 部分。

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

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

正确有效地实施这个模型是一个相当大的挑战,但那是另一回事了。

【讨论】:

  • 我知道函数式反应式编程。它似乎与我自己的研究(在交互式统计图形中)有关,我相信其中的许多想法都会对我的工作有所帮助。然而,我发现很难通过这种语言——我必须真正了解“指称语义”和“类型类态射”才能理解发生了什么吗?对该主题的一般观众介绍将非常有用。
  • @Conal:你清楚地知道你在说什么,但你的语言假设我拥有计算数学博士学位,而我没有。我确实有系统工程背景和 20 多年的计算机和编程语言经验,但我仍然觉得你的回答让我感到困惑。我挑战你用英文重新发布你的回复;-)
  • @minplay.dk:你的言论并没有给我太多关于你不明白的地方,而且我不愿意对你的特定英语子集做出疯狂的猜测'正在寻找。但是,我邀请您具体说明我上面的解释的哪些方面您遇到了绊脚石,以便我和其他人可以帮助您。例如,是否有您想要定义的特定词或您想要添加参考的概念?我真的很喜欢提高我的写作的清晰度和可访问性——而不是让它变得愚蠢。
  • "Determinacy"/"determinate" 意味着有一个单一的、明确定义的正确值。相比之下,几乎所有形式的命令式并发都可以给出不同的答案,这取决于调度程序或您是否正在查看,它们甚至会死锁。 “语义”(更具体地说是“指称”)是指表达式或表示的值(“指称”),与“操作”(如何计算答案或由什么消耗多少空间和/或时间)形成对比一种机器)。
  • 我同意@mindplay.dk 的观点,尽管我不能吹嘘自己已经在这个领域工作了很长时间。尽管您似乎知道自己在说什么,但它并没有让我快速、简短和简单地理解这是什么,因为我已经被宠坏了,可以期待 SO。这个答案主要驱使我提出大量新问题而没有真正回答我的第一个问题。我希望分享在该领域仍然相对无知的经验可以让您了解您真正需要的简单和简短。顺便说一句,我来自与 OP 相似的背景。
【解决方案2】:

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

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

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

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

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 的框,并跟踪它移动到哪里。

这是一个不错的paper on functional reactive programming

【讨论】:

  • 那么反应式编程是声明式编程的一种形式吗?
  • > 那么反应式编程是声明式编程的一种形式吗? 函数式响应式编程是函数式编程的一种形式,是声明式编程的一种形式。
  • @user712092 不是,不是。例如,如果我用你的宏在 C 中调用 sqrt(x),那只会计算 sqrt(mouse_x()) 并返回一个双精度值。在真正的函数式反应系统中,sqrt(x) 将返回一个新的“随时间加倍”。如果您要尝试使用 #define 模拟 FR 系统,您几乎必须放弃变量以支持宏。 FR 系统通常也只会在需要重新计算时才重新计算,而使用宏意味着您将不断地重新评估所有内容,一直到子表达式。
  • “对于许多类型的软件(例如,任何与用户交互的软件),副作用在某种程度上是必要的。”而且也许只在实施层面。纯粹的惰性函数式编程的实现有很多副作用,而范式的成功之一就是将许多副作用排除在编程模型之外。我自己对功能性用户界面的尝试表明,它们也可以完全编程而没有副作用。
  • @tieTYT x 永远不会重新分配/变异。 x 的值是随时间变化的值序列。另一种看待它的方式是,x 的值不是像数字那样具有“正常”值,而是(在概念上)一个将时间作为参数的函数。 (这有点过于简单化了。您无法创建时间值来预测鼠标位置等事物的未来。)
【解决方案3】:

获得初步直觉的一种简单方法是想象您的程序是一个电子表格,而您的所有变量都是单元格。如果电子表格中的任何单元格发生更改,则引用该单元格的任何单元格也会更改。玻璃钢也是如此。现在想象一些单元格自己改变(或者更确切地说,是从外部世界获取的):在 GUI 情况下,鼠标的位置就是一个很好的例子。

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

【讨论】:

  • 一个非常恰当的例子。有理论的东西很好,也许有些人在不求助于一个基础示例的情况下得到了它的含义,但我需要从它对我的作用开始,而不是它抽象地是什么。我最近才得到的(来自 Netflix 的 Rx 演讲!)是 RP(或 Rx,无论如何),使这些“变化的值”成为头等舱,并让您对它们进行推理,或编写与它们一起做事的函数。如果您愿意,可以编写函数来创建电子表格或单元格。它会在值结束(消失)时进行处理,并让您自动清理。
  • 这个例子强调了事件驱动编程和反应式方法之间的区别,你只需声明依赖关系就可以使用智能路由。
【解决方案4】:

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

  1. 在数学中x = sin(t) 的意思是,xsin(t)不同名称。所以写x + ysin(t) + y 是一回事。函数式反应式编程在这方面就像数学:如果您编写 x + y,它会根据 t 在使用时的值进行计算。
  2. 在类 C 编程语言(命令式语言)中,x = sin(t) 是一个赋值:这意味着 x 存储在赋值时取得的sin(t)

【讨论】:

  • 很好的解释。我认为您还可以添加 FRP 意义上的“时间”通常是“来自外部输入的任何变化”。每当外力改变 FRP 的输入时,您已经将“时间”向前移动,并重新计算受更改影响的所有内容。
  • 在数学中x = sin(t) 表示x 是给定tsin(t) 的值。它不是 sin(t) 作为函数的不同名称。否则它将是x(t) = sin(t)
  • +Dmitri Zaitsev 等号在数学中有多种含义。其中之一是每当您看到左侧时,您都可以将其与右侧交换。例如2 + 3 = 5a**2 + b**2 = c**2
【解决方案5】:

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

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

具有来自该“体积值”节点的边缘的各种节点将自动被触发,并且任何必要的计算和更新都会自然地在应用程序中产生波动。应用程序对用户刺激做出“反应”。函数式反应式编程只是用函数式语言或通常在函数式编程范式中实现这一想法。

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

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

节点可以被视为并行触发,但它们通常是串行执行或以有限的并行度执行(例如,可能有几个线程在执行它们)。一个著名的例子是Manchester Dataflow Machine,它(IIRC)使用标记数据架构通过一个或多个执行单元来调度图中节点的执行。数据流计算非常适合异步触发计算以产生级联计算的情况,而不是尝试由一个(或多个时钟)控制执行。

反应式编程引入了这种“级联执行”的思想,并且似乎以类似数据流的方式来考虑程序,但前提是一些节点与“外部世界”挂钩,并触发了级联执行当这些类似感觉的节点发生变化时。程序执行看起来就像一个复杂的反射弧。该程序可能在刺激之间基本无动于衷,也可能不会在刺激之间进入基本无动状态。

“非反应性”编程将是对执行流程和与外部输入的关系有非常不同的看法的编程。这可能有点主观,因为人们可能会倾向于说出任何对外部输入做出“反应”的东西。但是从本质上看,以固定间隔轮询事件队列并将发现的任何事件分派给函数(或线程)的程序反应性较小(因为它只以固定间隔处理用户输入)。同样,这也是这里的精神:可以想象将具有快速轮询间隔的轮询实现置于非常低级别的系统中,并在其之上以反应方式编程。

【讨论】:

  • 好的,现在上面有一些很好的答案。我应该删除我的帖子吗?如果我看到两三个人说它没有增加任何东西,我会删除它,除非它的有用数量增加。除非它增加了一些价值,否则将它留在这里是没有意义的。
  • 你提到了数据流,所以恕我直言,这增加了一些价值。
  • 这就是 QML 的本意,看起来 ;)
  • 对我来说,这个答案是最容易理解的,尤其是因为使用了“通过应用程序产生涟漪”和“类感官节点”等自然类似物。太好了!
  • 很遗憾,Manchester Dataflow Machine 链接已失效。
【解决方案6】:

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

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

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

一个常见的答案是“FRP 就是描述一个系统 时变函数而不是可变状态的术语”,并且 肯定不会错。这是语义的观点。但在 我认为,更深层次、更令人满意的答案是由 遵循纯粹的句法标准:

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

以计数器为例:你有两个按钮 标记为“向上”和“向下”,可用于递增或递减 柜台。当务之急,您将首先指定一个初始值 然后在按下按钮时更改它;像这样:

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

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

编辑:

在原始答案之后大约 2 年,最近我得出结论,FRP 实施还有另一个重要方面。他们需要(并且通常会)解决一个重要的实际问题:缓存失效

x_i-s 的方程描述了一个依赖图。当x_i 中的一些在j 发生变化时,j+1 处的所有其他x_i' 值不需要更新,因此不需要重新计算所有依赖项,因为一些x_i' 可能独立于@ 987654341@。

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

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

【讨论】:

  • 我一直在你身边,直到你使用 离散 方程。 FRP的创始思想是连续时间,这里没有“j+1”。相反,想想连续时间的函数。正如牛顿、莱布尼茨和其他人向我们展示的那样,使用积分和 ODE 系统以微分但连续的方式描述这些函数通常非常方便(并且从字面意义上“自然”)。否则,您描述的是一种近似算法(而且是一个糟糕的算法),而不是事物本身。
  • HTML模板和布局约束语言layx似乎表达了FRP的元素。
  • @Conal 这让我想知道 FRP 与 ODE 有何不同。它们有何不同?
  • @jhegedus 在这种集成中(可能是递归的,即 ODE)提供了 FRP 的构建块之一,而不是全部。 FRP 词汇表的每一个元素(包括但不限于积分)都用连续时间精确解释。这种解释有帮助吗?
【解决方案7】:

Conal Elliott 的论文 Simply efficient functional reactivity (direct PDF, 233 KB) 是一个相当不错的介绍。相应的库也可以。

这篇论文现在被另一篇论文所取代,Push-pull functional reactive programming (direct PDF, 286 KB)。

【讨论】:

    【解决方案8】:

    免责声明:我的答案是在 rx.js 的上下文中 - 一个用于 Javascript 的“反应式编程”库。

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

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

    • Observable 是 Iterable 的对偶。

    一个 Iterable 是一个惰性消耗的序列 - 每个项目在它想要使用它时被迭代器拉取,因此枚举是由消费者驱动的。

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

    【讨论】:

    • 非常感谢您对 observable 的直接定义 以及它与 iterable 的区别。我认为将一个复杂的概念与其众所周知的对偶概念进行比较通常会很有帮助,从而获得真正的理解。
    • “因此,FRP 背后的想法是,与其处理每个单独的事件,不如创建一个事件流(使用 observable* 实现)并对其应用 HoF。”我可能弄错了,但我相信这实际上不是 FRP,而是对观察者设计模式的一个很好的抽象,它允许通过 HoF 进行功能操作(这很棒!),同时仍然打算与命令式代码一起使用。话题讨论-lambda-the-ultimate.org/node/4982
    【解决方案9】:

    伙计,这真是一个绝妙的主意!为什么我在 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()
    

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

    【讨论】:

    【解决方案10】:

    Paul Hudak 的书,The Haskell School of Expression,不仅是对 Haskell 的精彩介绍,而且在 FRP 上花费了相当多的时间。如果您是 FRP 的初学者,我强烈推荐它让您了解 FRP 的工作原理。

    这本书看起来也有新的重写(2011 年发布,2014 年更新)The Haskell School of Music

    【讨论】:

      【解决方案11】:

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

      【讨论】:

        【解决方案12】:

        如前所述,其作用类似于电子表格。通常基于事件驱动的框架。

        与所有“范式”一样,它的新颖性值得商榷。

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

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

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

        一切都很有趣:)。

        【讨论】:

          【解决方案13】:

          我在 Clojure 子版块上发现了这个关于 FRP 的精彩视频。即使您不了解 Clojure,也很容易理解。

          这是视频:http://www.youtube.com/watch?v=nket0K1RXU4

          这是第二部分视频所指的来源:https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs

          【讨论】:

            【解决方案14】:

            Andre Staltz 的This article 是迄今为止我见过的最好、最清晰的解释。

            文章中的一些引用:

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

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

            以下是文章中精彩图表的示例:

            【讨论】:

              【解决方案15】:

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

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

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

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

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

              想想数学中的 A=B+C 和程序中的 A=B+C 之间的区别。 在数学中,您正在描述一种永远不会改变的关系。在一个程序中,它说“现在”A 是 B+C。但是下一个命令可能是 B++,在这种情况下 A 不等于 B+C。在数学或声明式编程中,无论您询问什么时间点,A 始终等于 B+C。

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

              EventStream 是 EventStream + 一些转换函数。

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

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

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

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

              引自 - Deprecating The Observer Pattern http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

              【讨论】:

              • 这正是我对声明式编程的感受,你只是比我描述的更好。
              【解决方案16】:

              关于响应式编程的简短而清晰的解释出现在Cyclejs - Reactive Programming,它使用简单和直观的示例。

              A [module/Component/object] 是响应式的意味着它完全负责 通过对外部事件做出反应来管理自己的状态。

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

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

              【讨论】:

                【解决方案17】:

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

                例如,您可以编写一个 Linq 查询,例如 (来自 MyObservableSetOfMouseMovements 中的 m 其中 m.X

                使用 Rx 扩展,就是这样:您的 UI 代码可以对传入的鼠标移动流做出反应,并在您处于 100,100 框时进行绘制...

                【讨论】:

                  【解决方案18】:

                  FRP 是函数式编程(基于一切都是函数的思想的编程范式)和反应式编程范式(基于一切都是流的思想(观察者和可观察的哲学))的组合。它应该是世界上最好的。

                  查看 Andre Staltz 关于响应式编程的帖子。

                  【讨论】:

                    猜你喜欢
                    • 2013-07-24
                    • 2014-05-09
                    • 2010-11-10
                    • 2010-09-13
                    • 2013-11-20
                    • 1970-01-01
                    • 2017-01-04
                    • 2015-08-21
                    • 2010-11-06
                    相关资源
                    最近更新 更多