90

我试图在 Haskell 中可视化一些简单的自动物理系统(如钟摆、机械臂等)。通常这些系统可以用如下等式来描述

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

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

因此,我拿起了 Paul Hudak 的书“The Haskell School of Expression”,发现那里介绍的领域特定语言“FAL”(用于功能动画语言)实际上对我的简单玩具系统非常有效(尽管有些功能,特别是integrate,似乎有点懒惰而无法有效使用,但很容易修复)。

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

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

  1. “反应式”的状态,来自 Conal Eliott 的项目,他(据我所知)是这种编程范式的发明者之一,看起来有点陈旧。我喜欢他的代码,但也许我应该尝试其他更新的替代方案?在语法/性能/运行时稳定性方面,它们之间的主要区别是什么?

  2. 引用 2011 年的一项调查,第 6 节,“ ...... FRP 实现在性能上仍然不够高效或可预测,无法在需要延迟保证的领域中有效使用...... ”。尽管调查提出了一些有趣的可能优化,但考虑到 FRP 已经存在超过 15 年这一事实,我的印象是,至少在几年内,这个性能问题可能非常难以解决,甚至本来就难以解决。这是真的?

  3. 该调查的同一作者在他的博客中谈到了“时间泄漏” 。这个问题是 FRP 独有的,还是我们在使用纯非严格语言编程时通常遇到的问题?如果性能不够,您是否曾经发现随着时间的推移稳定基于 FRP 的系统太难了?

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

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

4

3 回答 3

83

现在主要有两个实用的 Haskell 库用于函数式反应式编程。两者都由一个人维护,但也收到了其他 Haskell 程序员的代码贡献:

  • Netwire专注于效率、灵活性和可预测性。它有自己的事件范式,可用于传统 FRP 无法工作的领域,包括网络服务和复杂的模拟。风格:应用和/或箭头。最初的作者和维护者:Ertugrul Söylemez(这是我)。

  • reactive-banana建立在传统的 FRP 范式之上。虽然使用它很实用,但它也可以作为经典 FRP 研究的基础。它的主要重点是用户界面,并且有一个现成的 wx 界面。风格:适用。最初的作者和维护者:Heinrich Apfelmus。

您应该尝试这两种方法,但根据您的应用程序,您可能会发现其中一种更合适。

对于游戏、网络、机器人控制和模拟,您会发现 Netwire 非常有用。它为这些应用配备了现成的电线,包括各种有用的微分、积分和许多用于透明事件处理的功能。有关教程,请访问Control.Wire我链接的页面上的模块文档。

对于图形用户界面,目前最好的选择是响应式香蕉。它已经有一个 wx 接口(作为一个单独的库 reactive-banana-wx),并且 Heinrich 在此上下文中写了很多关于 FRP 的博客,包括代码示例。

回答您的其他问题:FRP 不适用于需要实时可预测性的场景。这主要归功于 Haskell,但不幸的是,FRP 很难在低级语言中实现。一旦 Haskell 本身成为实时就绪,FRP 也将到达那里。从概念上讲,Netwire 已为实时应用程序做好了准备。

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

我知道目前没有使用 FRP 的商业或其他大型项目。图书馆已经准备好,但我认为人们还没有——还没有。

于 2012-11-12T13:08:33.067 回答
23

尽管已经有一些很好的答案,但我将尝试回答您的具体问题。

  1. 由于时间泄漏问题,reactive 不适用于严肃的项目。(见#3)。当前设计最相似的库是响应式香蕉,它是在响应式的启发下开发的,并与 Conal Elliott 进行了讨论。

  2. 尽管 Haskell 本身不适用于硬实时应用程序,但在某些情况下可以将 Haskell 用于软实时应用程序。我不熟悉当前的研究,但我不认为这是一个无法克服的问题。我怀疑像 Yampa 这样的系统,或者像 Atom 这样的代码生成系统,可能是解决这个问题的最佳方法。

  3. “时间泄漏”是可切换 FRP 特有的问题。当系统无法释放旧对象时会发生泄漏,因为如果将来某个时间发生切换,它可能需要它们。除了内存泄漏(可能非常严重)之外,另一个后果是,当切换发生时,系统必须暂停,同时遍历旧对象链以生成当前状态。

不可切换的 frp 库(例如 Yampa 和旧版本的反应香蕉)不会受到时间泄漏的影响。可切换的 frp 库通常采用以下两种方案之一:要么它们具有特殊的“创建单子”,在其中创建 FRP 值,要么它们使用“老化”类型参数来限制可以发生切换的上下文。 elerea(可能还有 netwire?)使用前者,而最近的反应性香蕉和葡萄柚使用后者。

我所说的“可切换frp”是指实现Conal的功能switcher :: Behavior a -> Event (Behavior a) -> Behavior a或相同语义的一种。这意味着网络的形状可以在运行时动态切换。

这并不真正与@ertes 关于 monadic 接口的声明相矛盾:事实证明,Monad为 an 提供实例Event会使时间泄漏成为可能,并且使用上述任何一种方法都不再可能定义等效的 Monad 实例。

最后,尽管 FRP 还有很多工作要做,但我认为一些较新的平台(reactive-banana、elerea、netwire)足够稳定和成熟,您可以从中构建可靠的代码。但是您可能需要花费大量时间来学习细节,以了解如何获得良好的性能。

于 2012-11-12T15:12:24.830 回答
20

我将列出 Mono 和 .Net 空间中的一些项目,以及我不久前发现的 Haskell 空间中的一个项目。我将从 Haskell 开始。

榆树 -链接

根据其网站的描述:

Elm 旨在让前端 Web 开发更愉快。它引入了一种新的 GUI 编程方法,可以纠正 HTML、CSS 和 JavaScript 的系统性问题。Elm 允许您快速轻松地使用可视化布局、使用画布、管理复杂的用户输入以及逃离回调地狱。

它有自己的FRP变体。从玩它的例子来看,它似乎很成熟。

反应式扩展 -链接

其首页的描述:

Reactive Extensions (Rx) 是一个库,用于使用可观察序列和 LINQ 样式的查询运算符组成异步和基于事件的程序。使用 Rx,开发人员可以使用 Observables 表示异步数据流,使用 LINQ 运算符查询异步数据流,并使用调度程序参数化异步数据流中的并发性。简单地说,Rx = Observables + LINQ + Schedulers。

Reactive Extensions 来自 MSFT,实现了许多简化事件处理的优秀操作符。它是在几天前开源的。它非常成熟并用于生产;在我看来,对于 Windows 8 API,它会是比 TPL 库提供的更好的 API;因为 observables 既可以是热的也可以是冷的,可以重试/合并等,而任务总是代表热的或已完成的计算,这些计算要么正在运行,要么出现故障,要么已完成。

我已经使用 Rx 为异步编写了服务器端代码,但我必须承认,在 C# 中编写函数式可能有点烦人。F# 有几个封装器,但很难跟踪 API 开发,因为该组相对封闭,不像其他项目那样由 MSFT 推广。

它的开源来自其 IL-to-JS 编译器的开源,因此它可能与 JavaScript 或 Elm 一起工作。

您可以使用消息代理(如 RabbitMQ 和 SocksJS)将 F#/C#/JS/Haskell 很好地绑定在一起。

Bling UI 工具包 -链接

其首页的描述:

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

免费LtU 文章

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

ReactiveUI -链接

Paul Betts,之前在 MSFT,现在在 Github,编写了这个框架。我已经非常广泛地使用它并且喜欢这个模型。它比 Blink 更解耦(本质上与使用 Rx 和它的抽象) - 使用它更容易对代码进行单元测试。Windows 的 github git 客户端就是用这个写的。

注释

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

显然 C# 和 F# 是经过严格评估的,所以这里的时间泄漏不是问题。JS也是一样。不过,这可能是可重放或缓存的 observables 的问题。

于 2012-11-12T13:26:31.527 回答