目前函数式反应型编程的实施情况如何?

我试图想象哈斯克尔一些简单的自动物理系统(如钟摆,机器人手臂等)。 这些系统通常可以用

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

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

所以我拿了 Paul Hudak 的《 Haskell 表达学校》 并且发现那里提供的领域特定语言“ FAL”(功能性动画语言)实际上对我的简单玩具系统非常有效(尽管一些功能,特别是 integrate,似乎有点懒惰,不能有效使用,但是很容易修复)。

我的问题是,对于当今更高级、甚至更实际的应用程序,哪种方法更成熟、更新、维护良好、性能调优?

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

  1. “被动反应”项目的状态看起来有点过时,该项目由科纳尔•艾略特(Conal Eliott)设计,据我所知,艾略特是这种编程范型的发明者之一。我喜欢他的代码,但也许我应该尝试其他更新的替代方案?就语法/性能/运行时稳定性而言,它们之间的主要区别是什么?

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

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

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

虽然我个人比较喜欢 Haskell 实现,但我也欢迎其他建议。例如,如果有一个 Erlang 的实现,那将是非常有趣的——这样就很容易有一个智能的、自适应的、自学习的服务器过程!

15004 次浏览

Right now there are mainly two practical Haskell libraries out there for functional reactive programming. Both are maintained by single persons, but are receiving code contributions from other Haskell programmers as well:

  • Netwire focusses on efficiency, flexibility and predictability. It has its own event paradigm and can be used in areas where traditional FRP does not work, including network services and complex simulations. Style: applicative and/or arrowized. Initial author and maintainer: Ertugrul Söylemez (this is me).

  • reactive-banana builds on the traditional FRP paradigm. While it is practical to use it also serves as ground for classic FRP research. Its main focus is on user interfaces and there is a ready-made interface to wx. Style: applicative. Initial author and maintainer: Heinrich Apfelmus.

You should try both of them, but depending on your application you will likely find one or the other to be a better fit.

For games, networking, robot control and simulations you will find Netwire to be useful. It comes with ready-made wires for those applications, including various useful differentials, integrals and lots of functionality for transparent event handling. For a tutorial visit the documentation of the Control.Wire module on the page I linked.

For graphical user interfaces currently your best choice is reactive-banana. It already has a wx interface (as a separate library reactive-banana-wx) and Heinrich blogs a lot about FRP in this context including code samples.

To answer your other questions: FRP isn't suitable in scenarios where you need real-time predictability. This is largely due to Haskell, but unfortunately FRP is difficult to realize in lower level languages. As soon as Haskell itself becomes real-time-ready, FRP will get there, too. Conceptually Netwire is ready for real-time applications.

Time leaks aren't really a problem anymore, because they are largely related to the monadic framework. Practical FRP implementations simply don't offer a monadic interface. Yampa has started this and Netwire and reactive-banana both build on that.

I know of no commercial or otherwise large scale projects using FRP right now. The libraries are ready, but I think the people aren't – yet.

I'm going to list a couple of items in the Mono and .Net space and one from the Haskell space that I found not too long ago. I'll start with Haskell.

Elm - link

Its description as per its site:

Elm aims to make front-end web development more pleasant. It introduces a new approach to GUI programming that corrects the systemic problems of HTML, CSS, and JavaScript. Elm allows you to quickly and easily work with visual layout, use the canvas, manage complicated user input, and escape from callback hell.

It has its own variant of FRP. From playing with its examples it seems pretty mature.

Reactive Extensions - link

Description from its front page:

The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators. Using Rx, developers represent asynchronous data streams with Observables, query asynchronous data streams using LINQ operators, and parameterize the concurrency in the asynchronous data streams using Schedulers. Simply put, Rx = Observables + LINQ + Schedulers.

Reactive Extensions comes from MSFT and implements many excellent operators that simplify handling events. It was open sourced just a couple of days ago. It's very mature and used in production; in my opinion it would have been a nicer API for the Windows 8 APIs than the TPL-library provides; because observables can be both hot and cold and retried/merged etc, while tasks always represent hot or done computations that are either running, faulted or completed.

I've written server-side code using Rx for asynchronocity, but I must admit that writing functionally in C# can be a bit annoying. F# has a couple of wrappers, but it's been hard to track the API development, because the group is relatively closed and isn't promoted by MSFT like other projects are.

Its open sourcing came with the open sourcing of its IL-to-JS compiler, so it could probably work well with JavaScript or Elm.

You could probably bind F#/C#/JS/Haskell together very nicely using a message broker, like RabbitMQ and SocksJS.

Bling UI Toolkit - link

Description from its front page:

Bling is a C#-based library for easily programming images, animations, interactions, and visualizations on Microsoft's WPF/.NET. Bling is oriented towards design technologists, i.e., designers who sometimes program, to aid in the rapid prototyping of rich UI design ideas. Students, artists, researchers, and hobbyists will also find Bling useful as a tool for quickly expressing ideas or visualizations. Bling's APIs and constructs are optimized for the fast programming of throw away code as opposed to the careful programming of production code.

Complimentary LtU-article.

I've tested this, but not worked with it for a client project. It looks awesome, has nice C# operator overloading that form the bindings between values. It uses dependency properties in WPF/SL/(WinRT) as event sources. Its 3D animations work well on reasonable hardware. I would use this if I end up on a project in need for visualizations; probably porting it to Windows 8.

ReactiveUI - link

Paul Betts, previously at MSFT, now at Github, wrote that framework. I've worked with it pretty extensively and like the model. It's more decoupled than Blink (by its nature from using Rx and its abstractions) - making it easier to unit test code using it. The github git client for Windows is written in this.

Comments

The reactive model is performant enough for most performance-demanding applications. If you are thinking of hard real-time, I'd wager that most GC-languages have problems. Rx, ReactiveUI create some amount of small object that need to be GCed, because that's how subscriptions are created/disposed and intermediate values are progressed in the reactive "monad" of callbacks. In general on .Net I prefer reactive programming over task-based programming because callbacks are static (known at compile time, no allocation) while tasks are dynamically allocated (not known, all calls need an instance, garbage created) - and lambdas compile into compiler-generated classes.

Obviously C# and F# are strictly evaluated, so time-leak isn't a problem here. Same for JS. It can be a problem with replayable or cached observables though.

Although there are some good answers already, I'm going to attempt to answer your specific questions.

  1. reactive is not usable for serious projects, due to time leak problems. (see #3). The current library with the most similar design is reactive-banana, which was developed with reactive as an inspiration, and in discussion with Conal Elliott.

  2. Although Haskell itself is inappropriate for hard real-time applications, it is possible to use Haskell for soft realtime applications in some cases. I'm not familiar with current research, but I don't believe this is an insurmountable problem. I suspect that either systems like Yampa, or code generation systems like Atom, are possibly the best approach to solving this.

  3. A "time leak" is a problem specific to switchable FRP. The leak occurs when a system is unable to free old objects because it may need them if a switch were to occur at some point in the future. In addition to a memory leak (which can be quite severe), another consequence is that, when the switch occurs, the system must pause while the chain of old objects is traversed to generate current state.

Non-switchable frp libraries such as Yampa and older versions of reactive-banana don't suffer from time leaks. Switchable frp libraries generally employ one of two schemes: either they have a special "creation monad" in which FRP values are created, or they use an "aging" type parameter to limit the contexts in which switches can occur. elerea (and possibly netwire?) use the former, whereas recent reactive-banana and grapefruit use the latter.

By "switchable frp", I mean one which implements Conal's function switcher :: Behavior a -> Event (Behavior a) -> Behavior a, or identical semantics. This means that the shape of the network can dynamically switch as it's run.

This doesn't really contradict @ertes's statement about monadic interfaces: it turns out that providing a Monad instance for an Event makes time leaks possible, and with either of the above approaches it's no longer possible to define the equivalent Monad instances.

Finally, although there's still a lot of work remaining to be done with FRP, I think some of the newer platforms (reactive-banana, elerea, netwire) are stable and mature enough that you can build reliable code from them. But you may need to spend a lot of time learning the ins and outs in order to understand how to get good performance.