我读过维基百科上关于响应式编程的文章。我还读过一篇关于函数式响应式编程的小文章。这些描述相当抽象。
函数式响应式编程(FRP)在实践中意味着什么? 反应式编程(相对于非反应式编程?)由什么组成?
我的背景是命令式/OO语言,所以与此范例相关的解释将受到赞赏。
我读过维基百科上关于响应式编程的文章。我还读过一篇关于函数式响应式编程的小文章。这些描述相当抽象。
函数式响应式编程(FRP)在实践中意味着什么? 反应式编程(相对于非反应式编程?)由什么组成?
我的背景是命令式/OO语言,所以与此范例相关的解释将受到赞赏。
当前回答
对我来说,这是关于符号的2个不同的含义=:
在数学中,x = sint的意思是,x是sint的另一个名字。所以写x + y和sin(t) + y是一样的。函数式响应式编程在这方面就像数学:如果你写x + y,它是用t在使用时的任何值来计算的。 在类c编程语言(命令式语言)中,x = sin(t)是一个赋值:它意味着x存储在赋值时所取的sin(t)的值。
其他回答
Paul Hudak的书,The Haskell School of Expression,不仅是对Haskell的很好的介绍,而且还花了相当多的时间在FRP上。如果你是FRP的初学者,我强烈推荐它让你了解FRP是如何工作的。
还有一本看起来像是这本书(2011年出版,2014年更新)的新重写版——哈斯克尔音乐学院。
看看Rx, net的响应式扩展。他们指出,使用IEnumerable,你基本上是从流中“拉”出来的。IQueryable/IEnumerable上的Linq查询是集合操作,从集合中“吸”出结果。但是在IObservable上使用相同的操作符,你可以编写“反应”的Linq查询。
例如,您可以编写这样的Linq查询 (from MyObservableSetOfMouseMovements中的m m.X<100 m.Y<100 选择新的点(m.X,m.Y))。
有了Rx扩展,就是这样:你有UI代码,它会对传入的鼠标移动流做出反应,并在你处于100,100框时进行绘制……
免责声明:我的答案是在rx.js的上下文中给出的——一个用于Javascript的“响应式编程”库。
在函数式编程中,不是遍历集合的每个项,而是对集合本身应用高阶函数(hof)。因此,FRP背后的思想是,与其处理每个单独的事件,不如创建一个事件流(使用可观察对象*实现),并对其应用HoFs。通过这种方式,您可以将系统可视化为连接发布者和订阅者的数据管道。
The major advantages of using an observable are: i) it abstracts away state from your code, e.g., if you want the event handler to get fired only for every 'n'th event, or stop firing after the first 'n' events, or start firing only after the first 'n' events, you can just use the HoFs (filter, takeUntil, skip respectively) instead of setting, updating and checking counters. ii) it improves code locality - if you have 5 different event handlers changing the state of a component, you can merge their observables and define a single event handler on the merged observable instead, effectively combining 5 event handlers into 1. This makes it very easy to reason about what events in your entire system can affect a component, since it's all present in a single handler.
可观察对象是可迭代对象的对偶。
Iterable是一个惰性消费序列——迭代器在需要使用每个项时都会拉出它,因此枚举是由消费者驱动的。
可观察对象是一个惰性生成的序列——每一项在被添加到序列时都被推送给观察者,因此枚举是由生产者驱动的。
Andre Staltz的这篇文章是迄今为止我所见过的最好、最清楚的解释。
以下是文章中的一些引述:
响应式编程是使用异步数据流进行编程。 最重要的是,你会得到一个神奇的功能工具箱来组合、创建和过滤任何这些流。
下面是文章中精彩图表的一个例子:
在阅读了许多页关于FRP的文章后,我终于看到了这篇关于FRP的启发性文章,它最终让我明白了FRP的真正含义。
下面我引用海因里希·阿费尔马斯(活性香蕉的作者)的话。
What is the essence of functional reactive programming? A common answer would be that “FRP is all about describing a system in terms of time-varying functions instead of mutable state”, and that would certainly not be wrong. This is the semantic viewpoint. But in my opinion, the deeper, more satisfying answer is given by the following purely syntactic criterion: The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration. For instance, take the example of a counter: you have two buttons labelled “Up” and “Down” which can be used to increment or decrement the counter. Imperatively, you would first specify an initial value and then change it whenever a button is pressed; something like this: counter := 0 -- initial value on buttonUp = (counter := counter + 1) -- change it later on buttonDown = (counter := counter - 1) The point is that at the time of declaration, only the initial value for the counter is specified; the dynamic behavior of counter is implicit in the rest of the program text. In contrast, functional reactive programming specifies the whole dynamic behavior at the time of declaration, like this: counter :: Behavior Int counter = accumulate ($) 0 (fmap (+1) eventUp `union` fmap (subtract 1) eventDown) Whenever you want to understand the dynamics of counter, you only have to look at its definition. Everything that can happen to it will appear on the right-hand side. This is very much in contrast to the imperative approach where subsequent declarations can change the dynamic behavior of previously declared values.
所以,在我的理解中,FRP程序是一组方程:
J是离散的:1,2,3,4…
F依赖于t所以这包含了外部刺激模型的可能性
程序的所有状态都封装在变量x_i中
FRP库考虑了进度时间,换句话说,从j到j+1。
我会在这个视频中更详细地解释这些方程。
编辑:
在最初的回答大约2年后,最近我得出结论,FRP实现还有另一个重要的方面。它们需要(通常也会)解决一个重要的实际问题:缓存失效。
x_i-s的方程描述了一个依赖关系图。当x_i在j时刻发生变化时,并不需要更新j+1时刻的所有其他x_i'值,因此并不需要重新计算所有依赖项,因为有些x_i'可能与x_i无关。
而且,改变的x_i-s可以被增量更新。例如,让我们考虑Scala中的映射操作f=g.map(_+1),其中f和g是int类型的列表。这里f对应于x_i(t_j) g是x_j(t_j)现在,如果我将一个元素前置到g中,那么对g中的所有元素执行映射操作将是浪费的。一些FRP实现(例如reflect - FRP)旨在解决这个问题。这个问题也称为增量计算。
换句话说,FRP中的行为(x_i-s)可以被认为是缓存的计算。如果某些f_i-s确实发生了变化,FRP引擎的任务就是有效地使这些缓存(x_i-s)失效并重新计算。