Writing functional applications with Functional Reactive Programming and Bacon.js
Update: on March 29 I updated the code for this app to make it clearer and more readable.
In my previous post I discussed the concepts behind functional programming. This blog will demonstrate how we can use these concepts to build applications with the help of Bacon.js — a Functional Reactive Programming library by Juha Paananen.
Themes of functional programming
It’s important to remember the key themes of functional programming:
- Computation as the application of functions
- Avoiding side effects
Bacon.js provides a set of abstractions that allow us to program in a way that matches these themes.
What is Functional Reactive Programming?
Functional Reactive Programming (FRP) focuses on values that change over time. Since programming in a functional style means we have to avoid mutation and side effects this can make things difficult in environments where things are constantly changing. User interfaces are a good example of this since a UI will often change depending on user interaction. FRP provides an abstraction that allows us to program functionally but still be able to deal with constantly changing values.
Conal Elliott gave a very good answer to the question “What is functional reactive programming?” on Stack Overflow. These are the key points:
- Dynamic values are “first class” — they can be combined and passed in and out of functions
- Events are a discrete data type where each occurrence has an associated time and value
Bacon also has “properties” which is similar to an event stream but has a “current value”. As Juha explains on Bacon’s Github repo “things that change and have a current state are Properties, while things that consist of discrete events are EventStreams. You could think mouse clicks as an EventStream and mouse position as a Property.”
Building an application using Bacon.js
To demonstrate how we can use Bacon and FRP to write functional applications we will create a car grade and engine selector. This allows a user to choose a grade of a car and then a filtered list of engines available for that grade or vice versa. The application will have the following characteristics:
- The first list where the user makes a selection becomes the “primary” list. This means choosing an item in the secondary list does not filter the primary list
- On selection of a grade or engine the secondary list will be filtered according to the items available for that grade or engine
- The user should have a visual indication of which list is primary
- When the user chooses an item the total price is updated
- The user can reset all the choices they’ve made
In building this application I took a lot of inspiration from Juha’s implementation of the common TodoMVC app by Addy Osmani using Bacon.js. Juha’s app follows an MVC pattern and so did I for the grade and engine selector. You can view the application here.
Defining the model
The model uses a standard constructor which defines various properties on instantiation:
You’ll notice that one of these properties creates a new instance of the
Bacon.Bus class. This is a custom event stream which other streams can subscribe to (using
this.hide.plug(stream)) and we can push new values into the stream (using
Defining the collection
Similarly to collections in Backbone.js I have defined a collection object that contains all models of a similar type:
Defining the views
The application has two views: a List View and an Item View.
This view is created with an associated DOM element and a collection. On instantiation it creates child views and appends these new views to itself:
Once again we’ve used the
Bacon.Bus class to allow us to publish and subscribe values to this view. When the child views are created we subscribe the child’s
view.filter event stream (see below) to the list view’s
bus stream. We’ll see how this all ties together later on.
The item view is created with an associated DOM element and a model:
A new Bacon event stream is added as a property to the view when the user clicks the input control. You can transform event streams using Bacon’s
map method. In this particular case when a click occurs the view’s model is the value that is actually published.
SelectorModel we created a property called
hide which is a custom event stream. When this stream receives a value we can call a function using
onValue which in this view’s case either calls its
Tying it all together
There is an
initialise function which creates two collections:
This function handles the core logic of the app and is where we really see Bacon and FRP’s power come into play.
We start by creating two list views and adding an event stream to click events on the reset button. We then merge three streams together using Bacon’s
merge method: the
reset which is stored in the
gradeEngineList variable. This is a very good example of how Bacon represents these dynamic values as “first class” data types. The three streams are stored as a value and can be mixed and passed around as we see fit.
Another useful Bacon method is
scan. This creates a Bacon property which has an initial value and on each event applies a function to the current value that returns a new value which becomes the property’s “current value”. In our case the
gradeEngineListProp variable uses scan with an initial object literal value and the
This function receives the current value of the property as the first parameter. The second parameter is the value received by the
gradeEngineList stream so this is either the event object from the reset button being clicked or a
SelectorModel object. This function determines whether there is currently a “primary” list or not.
To filter a list we use the
onValue method of the
gradeEngineListProp property to determine which items need to be hidden and shown:
Once again the full application is available here.
You will notice that in building this application we have followed the principles of functional programming:
- Computation as the application of functions
- Avoiding side effects.
Using Bacon and FRP you can create apps that are simpler to write, referentially transparent and easier to test.