协慌网

登录 贡献 社区

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

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

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

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

答案

如果你想要了解 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 的精神上获得或多或少的东西。

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

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

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

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

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

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

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 一样。现在想象一些单元格自行改变(或者更确切地说,取自外部世界):在 GUI 情况下,鼠标的位置将是一个很好的例子。

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