当前功能响应式编程实现的现状是什么?

89

我试图在Haskell中可视化一些简单的自动物理系统(如摆,机器臂等)。这些系统通常可以用方程描述,例如

df/dt = c*f(t) + u(t)

其中u(t)代表某种形式的“智能控制”。这些系统看起来非常适合函数响应式编程范例。

因此,我找到了Paul Hudak的书《The Haskell School of Expression》,发现其中介绍的领域特定语言“FAL”(用于函数动画语言)实际上非常适合我的简单玩具系统(尽管某些函数,特别是integrate,似乎有点过于懒惰,不够高效,但很容易修复)。

我的问题是,对于更高级甚至实用的应用程序,有什么更成熟,最新,维护良好,性能调整的替代品?

这个维基页面列出了Haskell的几个选项,但我不清楚以下几个方面:

  1. "Reactive"这个项目来自Conal Eliott,他是这种编程范式的发明者之一(据我所知)。然而,这个项目似乎有点过时了。我喜欢他的代码,但也许我应该尝试其他更为更新的替代方案?它们主要的区别是什么,从语法/性能/运行稳定性方面来看?

  2. 引用2011年一项survey第6节的内容:“...... FRP实现的效率和性能仍然不足以在需要延迟保证的领域中有效使用......”。尽管该调查提出了一些有趣的可能优化措施,但由于FRP已经存在超过15年,我认为这种性能问题可能是非常或甚至本质上难以在未来几年内解决的。这是真的吗?

  3. 该调查的作者在他的blog中谈到了“时间泄漏”的问题。这个问题是FRP独有的,还是我们在使用纯粹的非严格语言编程时普遍存在的问题?如果性能不够好,你是否曾经发现过很难使基于FRP的系统稳定下来?

  4. 这仍然是一个研究级别的项目吗?像植物工程师、机器人工程师、金融工程师等人是否真正在使用它们(以适应他们的需求的任何语言)?

虽然我个人更喜欢使用Haskell实现,但我也愿意听取其他建议。例如,使用Erlang实现会特别有趣 --- 这样很容易拥有一个智能、自适应、自学习的服务器进程!

3个回答

83

目前主要有两个实用的Haskell库可用于函数响应式编程,它们都由单个人维护,但也收到其他Haskell程序员的代码贡献:

  • Netwire 侧重于效率、灵活性和可预测性。它拥有自己的事件范例,可用于传统FRP无法处理的领域,包括网络服务和复杂模拟。风格:applicative和/或arrowized。最初的作者和维护者:Ertugrul Söylemez(就是我)。

  • reactive-banana 建立在传统FRP范例上。虽然它很实用,但也为经典FRP研究提供了基础。其主要关注点在于用户界面,并有一个现成的wx接口。风格:applicative。最初的作者和维护者:Heinrich Apfelmus。

你应该尝试使用它们中的两个,但根据你的应用程序,你可能会发现其中一种更适合。

对于游戏、网络、机器人控制和模拟,你会发现Netwire非常有用。它附带了针对这些应用程序的现成“连线”,包括各种有用的微分、积分和大量透明事件处理功能。有关教程,请访问我链接的页面上的Control.Wire模块文档。

对于图形用户界面,目前最好的选择是reactive-banana。它已经有了一个wx接口(作为单独的库reactive-banana-wx),Heinrich在这个背景下博客了很多关于FRP的内容,包括代码示例。

回答您的其他问题:FRP不适用于需要实时可预测性的情况。这在很大程度上是由于Haskell造成的,但不幸的是,在低级语言中实现FRP很困难。只要Haskell本身准备好实时,FRP也会跟上。从概念上讲,Netwire已准备好应用于实时应用程序。

时间泄漏现在并不是一个问题了,因为它们与单子框架有很大关系。实用的FRP实现根本不提供单子接口。 Yampa已经开始了这个过程,Netwire和reactive-banana都在此基础上建立。

我目前不知道任何商业或其他大型项目正在使用FRP。虽然库已经准备好了,但我认为人们还没有准备好。


非常好的答案,谢谢。在您的库上实现一些强化学习算法将是一个有趣的练习。 - mnish
3
值得注意的是,最近一款使用Haskell编写的独立游戏(Nikki and the Robots)做出了决定——不使用FRP。 - Alex R

23
尽管已经有一些好的答案,但我将尝试回答您具体的问题。
1.由于时间泄漏问题,反应式编程不适用于严肃的项目。目前设计最相似的库是reactive-banana,它是在与Conal Elliott讨论的过程中受到反应式编程启发开发的。
2.虽然Haskell本身不适用于硬实时应用程序,但在某些情况下可以将其用于软实时应用程序。我不熟悉当前的研究,但我认为这不是一个无法克服的问题。我怀疑像Yampa这样的系统或像Atom这样的代码生成系统可能是解决这个问题的最佳方法。
3.“时间泄漏”是一个特定于可切换FRP的问题。当系统无法释放旧对象时,就会发生泄漏,因为在未来某个时刻可能需要它们来进行切换。除了内存泄漏(可能非常严重)之外,另一个后果是,在发生切换时,系统必须暂停,同时遍历旧对象链以生成当前状态。
非可切换的frp库(如Yampa和早期版本的reactive-banana)不会出现时间泄漏。可切换的frp库通常采用以下两种方案之一:要么使用一个特殊的“创建monad”来创建FRP值,要么使用一个“老化”类型参数来限制可以发生切换的上下文。elerea(可能还包括netwire?)采用前者,而最近的reactive-banana和grapefruit采用后者。
所谓“可切换的frp”,是指实现Conal的函数switcher :: Behavior a -> Event (Behavior a) -> Behavior a或具有相同语义的函数。这意味着网络的形状在运行时可以动态切换。
这并不真正与@ertes关于单调接口的说法相矛盾:事实证明,为Event提供Monad实例会导致时间泄漏,并且使用上述任何一种方法都无法定义等效的Monad实例。
最后,虽然FRP仍有许多工作需要完成,但我认为一些较新的平台(例如reactive-banana、elerea、netwire)已经足够稳定和成熟,您可以从中构建可靠的代码。但是,为了理解如何获得良好的性能,您可能需要花费大量时间学习其中的细节。

2
关于基于箭头的库(Yampa、netwire),它们也是可切换的。原因是箭头内置老化,你实际上无法摆脱它。(作为流转换器,箭头对其输入流的起始时间持中立态度。) - Heinrich Apfelmus
3
不要忘记reactive-banana的banana-nemesissodium - Dan Burton
1
@HeinrichApfelmus:这是一个有趣的观点。我通常不认为基于箭头的库可以像elerea/grapefruit/current-reactive-banana那样可切换。我认为它们的切换更接近于之前版本的reactive-banana所需的内容。不过这只是我的直觉,我还没有想得足够清楚,甚至无法描述我的意思。 - John L
2
@DanBurton 谢谢,我一直在试图回想起那个名字,但一直没有成功。我认为钠应该被视为现代 FRP 库之一,尽管它不像 reactive-banana 那样受欢迎。 - John L
虽然正在进行的讨论有点难以理解,但似乎表明只要垃圾回收时间在某种方式上可以被限制,软实时系统是确实可能的。无论如何,感谢您的出色答复。 - mnish

20

我将列出 Mono 和 .Net 领域中的几个项目以及最近发现的 Haskell 领域中的一个项目。我们从 Haskell 开始。

Elm - 链接

根据其网站的描述:

Elm 致力于使前端 Web 开发更加愉悦。 它引入了一种新的 GUI 编程方法,解决了 HTML,CSS 和 JavaScript 的系统问题。 Elm 可让您快速轻松地处理视觉布局,使用画布,管理复杂的用户输入并摆脱回调地狱。

它有自己的一套FRP变体。通过与其示例进行交互,它似乎相当成熟。

Reactive Extensions - 链接

来自其主页的说明:

Reactive Extensions (Rx) 是使用可观测序列和 LINQ 样式查询运算符来组合异步和基于事件编写程序的库。 使用 Rx,开发人员使用 Observables 表示异步数据流,使用 LINQ 运算符查询异步数据流,并使用 Schedulers 参数化异步数据流中的并发。 简而言之, Rx = Observables + LINQ + Schedulers。

Reactive Extensions 来自 MSFT,并实现了许多优秀的运算符,简化了处理事件。 它在几天前被开源。 它非常成熟并用于生产环境; 在我看来,它将是 Windows 8 API 的更漂亮的 API,因为可观测对象可以是热或冷、可以重试/合并等,而任务始终表示正在运行、失败或已完成的热或完成计算。

我已经使用 Rx 编写了服务器端代码以实现异步性,但我必须承认使用 C# 函数式编程有点烦人。F# 有一些包装器,但由于该团队相对封闭并且没有像其他项目一样得到微软的推广,因此很难跟踪 API 的开发。

随着其 IL-to-JS 编译器的开源,它很可能能够在 JavaScript 或 Elm 上很好地工作。

你可能可以使用消息代理(例如 RabbitMQ 和 SocksJS)将 F#/C#/JS/Haskell 绑定在一起,使它们非常完美地协同工作。

Bling UI 工具包 - link

它主页上的描述:

Bling 是一个基于 C# 的库,用于在 Microsoft 的 WPF/.NET 上轻松编程图像、动画、交互和可视化。Bling 面向设计技术人员,即有时进行编程的设计师,以帮助快速原型制作丰富的 UI 设计思想。学生、艺术家、研究人员和业余爱好者也会发现 Bling 在快速表达思想或可视化方面非常有用。Bling 的 API 和构造针对的是快速编程的丢弃代码,而不是仔细编写生产代码。

与此相关的LtU-article

我已经测试过这个工具包,但没有在客户项目中使用它。它看起来很棒,有漂亮的 C# 运算符重载,形成值之间的绑定。它在 WPF/SL/(WinRT) 中使用依赖属性作为事件源。其 3D 动画在合理的硬件上工作良好。如果我参与需要视觉化的项目,我会使用它;可能将其移植到 Windows 8。

ReactiveUI - link

Paul Betts之前在MSFT工作,现在在Github,他编写了这个框架。我已经与它密切合作并喜欢这种模型。 它比Blink更加解耦(从使用Rx和其抽象性的本质上)-使得使用它的代码单元测试更容易。Windows的github git客户端是用这个编写的。

注释

对于大多数性能要求苛刻的应用程序来说,反应模型足够高效。如果您考虑到硬实时,我敢打赌,大多数GC语言都会有问题。Rx,ReactiveUI创建一些需要进行GC的小对象,因为这些小对象是通过订阅/处理中间值在反应式“monad”中创建/处理的。总体而言,在.Net上,相对于基于任务的编程,我更喜欢反应式编程,因为回调是静态的(在编译时已知,无需分配),而任务是动态分配的(不确定,所有调用都需要一个实例,会创建垃圾)-并且lambdas会编译成由编译器生成的类。

显然,C#和F#是严格评估的,因此时间泄漏在这里不是问题。JS也是如此。但对于可重放或缓存的 observables 可能会有问题。


感谢您提供的出色答案。我喜欢Haskell FRP实现的一点是,它们似乎允许我清晰地解耦控制u(t)的计算和模拟f(t)的计算。F#实现是否也是如此呢? - mnish
我想你可以说这两个函数在时间上是解耦的,是的。但它们可能在逻辑上并没有解耦。 ;) - Henrik
据我所知,Reactive Extensions 和其他更精细的 UI-focused packages (事实上是 Haskell 以外的所有内容)仅使用事件语义--这意味着它们具有您可以切换的事件概念,而没有连续时间信号的概念,这些连续时间信号可以通过方程式相互交互。对于构建 GUIs 来说,这很好。然而,对于构建模拟和模型来说,这可能是不幸的。 - sclv
你是在暗示所有函数响应式编程库的实现都需要连续建模时间而不是离散建模吗?我找到了一篇名为“具有时间的过程代数:实时和离散时间”的论文 - 这是理解你所说的内容的好起点吗? - Henrik
我并不是说所有人都需要这样做,有些需要,有些则不需要。但那些需要的人更适合某些任务,而那些不需要的人则更适合其他任务... - sclv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接