Now that you have installed all the tools we need to create our first program using functional reactive programming, we can start. For this first program we will be using bacon.js.
The bacon.js lets you work with events (which it calls EventStream) and dynamic values (which it calls Property). An EventStream represents a stream of events (or data), and it is an observable object where you can subscribe to be notified of new events on this stream. Bacon comes with a lot of built-in functions to create event streams from different sources such as button clicks, key strokes, interval, arrays, promises, and so on (and you can also create your own sources). A Property is an observable. Like an EventStream, the difference between both is that a Property has a current value. So every time you need to know of the changes and the current state of something, you will be using a Property; if you just want to be notified of the events and you don't need to know the current state of something, then you use an EventStream. A Property can be created from an EventStream using the toProperty() or scan() methods from the bacon.js API.
Like any other functional reactive programming, Bacon has a set of operators to let you work with your events, so you can map an event to something else, you can filter some events, you can buffer your events, you can merge different event sources, and a whole lot more.
We will be running our first example on Node.js, so let's get started:
- Open your terminal and create a folder for this first project.
- Now create a project:
- Navigate to the folder you have created.
- Type the following command:
npm init
- Keep hitting Enter to accept the default configuration.
- If you did it right, you should see a message like this printed in your console:
{
"name": "example1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {}
}
OK, now we have our test project. In this project we will do a timer implementation–basically, what we want is to print the current time every second. We will do it with and without the functional reactive programming library, so first let's implement it in the traditional way.
Create a file called traditionalInterval.js and paste the following code inside it, and save:
setInterval(
()=> console.log(new Date())
,1000);
This code uses the function setInterval () to call our function every 1000 milliseconds (every second). Now, run this program using the following command in your terminal:
node ./traditionalInterval.js
If you did it right it will print the current date every second in your terminal, like this:
2016-10-30T20:28:22.778Z
2016-10-30T20:28:23.796Z
2016-10-30T20:28:24.803Z
2016-10-30T20:28:25.808Z
2016-10-30T20:28:26.809Z
2016-10-30T20:28:27.815Z
2016-10-30T20:28:28.820Z
To stop your program just hit Ctrl + C.
Now let's see how we can implement the same program using bacon.js for functional reactive programming. First, we need to install bacon.js on our project, as described in the Installation of tools section and also described here:
npm i [email protected] -save
With bacon.js installed in our project we are ready to implement our program. First create a file called baconInterval.js and then, as we will require the library in our project, open this file in a text editor and paste the following code:
var Bacon = require("baconjs");
Now that we have added Bacon to our code, we can use it to create a timer that prints the current time every second.
Before showing you the code that does this, it's important to understand how we model this problem using functional reactive programming. As we described before, bacon.js implements the concept of event streams, which are an observable stream of events. To create a timer, we need a special type of stream, capable of emitting events every x seconds, so we can listen to this stream to print the current date.
The bacon.js has a lot of built-in functions to create EventStreams from different sources. We will discuss the available functions later, but at this time we need to know the Bacon.interval() function that lets us create an EventStreams that emits an event every x seconds, where x is the time between the events in milliseconds. So, if we want to send an event every second, we must use it as follows:
Bacon.interval(1000);
This code creates an EventStream that emits an event every second. Now we need a way to be notified of the events in this stream so we can print the current date on the console. We can do this using the onValue() function–this function lets us subscribe to listen to events on this stream, and it receives the function to be executed as a parameter, so it lets us change our code to use it. The full code is as follows:
var Bacon = require("baconjs");
Bacon
.interval(1000)
.onValue(
()=> console.log(new Date())
);
If you run this code, you will see an output like this:
2016-10-30T20:28:22.778Z
2016-10-30T20:28:23.796Z
2016-10-30T20:28:24.803Z
2016-10-30T20:28:25.808Z
2016-10-30T20:28:26.809Z
2016-10-30T20:28:27.815Z
2016-10-30T20:28:28.820Z
You still have to hit Ctrl + C to stop your program.
Congratulations, you have just created your first program using functional reactive programming Now, let's change it a little.
We are going to implement the same code, but using a frp operator. To do this we will use the map () operator–this operator receives a mapping function as a parameter. This function takes an input value and transforms it in another value. Instead of listening to events on the stream and printing the current date on the console, we will listen to events in the stream, map those events to the current date, and then print the result. Create a file called baconIntervalMap.js and paste the following code:
var Bacon = require("baconjs");
Bacon
.interval(1000)
.map(()=>new Date())
.onValue((currentDate)=>console.log(currentDate));
If you run this code, you will see the same kind of output as from the previous code.
In the first example (without bacon.js) we have a hard code to test, because all logic of the code is tied together. In the next example, we improved our code testability, as we detached the source of the events from the action, so we can test our onValue() function mocking our EventStream, but the logic of getting the current date is still tied to the action (printing on the console). The last example (using map()) is a lot more testable, as we can test every single piece of code separately.
Let's see the last version of our problem. Instead of printing the current date every second forever, we will print the current date only five times, every second. We can implement it without using frp ; create a file called traditionalInterval5times.js and paste the following code:
var count = 0;
var intervalId = setInterval(()=>{
console.log(new Date());
count++;
if(count===5){
clearInterval(intervalId);
}
},1000);
Now our code has become a lot more complicated. To implement the new version of the proposed code we need an external counter (to make sure we run the code only five times), and we need a reference for the interval scheduler so we can stop it later. By looking at this code it is not easy to understand what it is trying to do, and as you probably already noted, it is really hard to test.
You might be wondering how we can change our last frp code to implement; it would be amazing if we had a way to only listen to five events on our Bacons EventStream, so I present to you the take() method. This method lets you listen to only a given number of events on an EventStream. So, changing our previous code to run only five times is really straightforward; create a file named baconIntervalMap5times.js and paste the following code:
var Bacon = require("baconjs");
Bacon
.interval(1000)
.take(5)
.map(()=>new Date())
.onValue((currentDate)=>console.log(currentDate));
Our new code is that simple. We don't need to change the stream source, the mapping function, nor the subscription function; we just need to take only five events from the stream.
In this final example, we can see how functional reactive programming can improve our code readability. In the code using setInterval(), we had to add a counter variable to make sure we ran the log function the right number of times. We also had to remember to clear the interval so we didn't run this code forever. On the other hand, on the code using frp all we had to do was describe the transformations using take() to show how many items we wanted, map() to get the current date, and finally subscribed to react to the events of this stream.
If things are going too fast for you, don't worry–I just wanted to get your hands dirty with some frp code. In the following chapters we will see these concepts in a lot more detail.