Comparing many FRP implementations by reimplementing the same toy app in each.
Interested in trying FRP (Functional Reactive Programming), but overwhelmed by the number of FRP libraries to choose from? To help you with this choice, this repository contains several implementations of the same small program, to give you a taste of what each library looks like.
|artery||scenarios 0 and 5, arrowized, signals||code|
|auto||scenarios 0 and 10, arrowized, signals||code|
|DysFRP||scenarios 0 and 10, higher-order, behaviours and events||code|
|elerea||scenarios 0 and 5, higher-order, signals||code|
|euphoria||scenario 0, higher-order, step signals, behaviours and events||code|
|FRPNow||scenario 0, higher-order, behaviours and events||code|
|grapefruit||scenario 0, higher-order, step signals, behaviours and events||code|
|machinecell||scenarios 0 and 5, arrowized, signals||code|
|netwire||all three scenarios, arrowized, continuous, signals||code|
|varying||all three scenarios, arrowized or applicative, continuous, signals||code|
|ordrea||scenario 0, higher-order, step signals, behaviours and events||code|
|reactive-bacon||scenarios 0 and 5, asynchronous data flow, behaviours and events||code|
|reactive-banana||scenario 0, higher-order, behaviours and events||code|
|reflex||scenarios 0 and 5, higher-order, behaviours and events||code|
|Yampa||scenarios 0 and 5, arrowized, continuous, signals||code|
|sodium||scenarios 0 and 5, higher-order, behaviours and events||deprecated|
For comparison, here are a few non-FRP implementations of the same small program.
|objective||scenario 0, push-pull automatons||code|
Instead of a Todo app, we use a small task which highlights the area in which FRP libraries differ the most: support for dynamic signal graphs.
Evan Czaplicki gave an excellent presentation about the different categories of FRP libraries, in which he categorized FRP libraries according to the choices they make in their attempt to support dynamic graphs. He described the following example:
Which number is displayed now? 0, because the new click counter hasn't received any click yet? 5, because the clicks did not reach the click counter while it was outside the graph? 10, because equational reasoning somehow dictates it?
The answer should not depend on the FRP library we choose, but on the behaviour we desire for our application! Thankfully, while each library gives a single answer for the above sequence of events, it is always possible to obtain the other outcomes using a different sequence of events. Our toy program will implement all three scenarios, but not necessarily by changing the underlying graph as described above.
A gloss window shall display six buttons, organized as three columns of two buttons. Each column implements one of the above scenarios: the first column chooses 0, the second column chooses 5, and the third column chooses 10. In each column, there is a button whose clicks are counted, and a toggle button to turn the click-counting on and off, starting with on. When off, the counter displays -1.
To be completely precise about the three scenarios:
In order to compare equivalent features in each library, the first half of the implementation should implement the required behaviour using only the core FRP features which are common to all FRP libraries: filtering events, combining current values, and accumulating changes to a local state.
To be completely precise about the expected static graph:
notoperations on a boolean, to establish whether the mode is currently on or off.
(+1)operations on an integer.
(+1)operations, depending on whether the incoming event is a toggle or a click.
-1is used when the mode is off.
In order to compare the parts of the libraries which differ from each other, this second half of the implementation should reimplement some of the scenarios from part one using dynamic graph modifications, when appropriate. Libraries which only support static graphs should skip this section (I hasten to point out that focusing on static graphs is a perfectly valid point in the design space, as explained in Evan's presentation).
For libraries which do support graph modifications, it is likely that only one of the scenarios can be implemented: temporarily remove the counter from the graph while the toggle is off, then try out the application to see which scenario occurs. In the unlikely case of a library which supports more than one way to modify its graph, more than one scenario might be implementable.
Due to the variability, I cannot give a more precise description of the task, but I do want to point out a common trap: don't reimplement first-order primitives using the higher-order ones. In particular:
Since the goal is to compare FRP implementations, not GUI systems, a simple implementation of the 6 buttons GUI is provided, along with methods to determine whether the current event is a click or a toggle. Gloss supports both IO-based and state-based APIs, which should make it easy to hook up any FRP library. See the existing implementations for details.
Evan's presentation classifies FRP libraries into four categories according to the choices they make regarding dynamic graphs. In our list of implementations at the top of this page, we tag each library with the category it belongs to, as well as the scenarios it can implement via dynamic graph changes. There are also other important distinctions between libraries which have nothing to do with dynamic graphs, whose corresponding tags are described in this section.
If the example app for your favourite FRP library is missing or non-idiomatic, or if there are other axes of comparison which you think should also be considered, feel free to open an issue or to send a pull request!