In my previous post I talked about how bad callbacks are and teased a path forward. In this post, I’ll start talking about that in detail.
Functional reactive, what?
Let’s get a bit of groundwork out of the way so that we’re all using the same vocabulary. First, let’s define what we mean by functional reactive programming.
Functional reactive programming is a development paradigm where you use pure functions (functions that don’t alter their inputs or maintain internal state) that react to values over time. One of the best analogies for this is the spreadsheet.
Consider a spreadsheet with 3 cells. Cell A1, A2, and A3. A1 and A2 accept a value and return it (like every blank cell in a spreadsheet), and A3 has a function that takes as input A1 and A2 and produces their sum. This system is functional reactive.
What makes this functional? As you feed in values, no values you fed in are changed, and the output is dependent on only the inputs we passed in not any hidden values.
What makes it reactive? As you make changes to A1 or A2, A3 immediately “reacts” to these changes and updates it output.
[Note: this definition is not completely accurate, but it’s close enough for our purposes without using words like monoid or morphism and causing everyone’s eyes to glaze over]
How is a spreadsheet going to help with Node?
In our spreadsheet example, if we put 2 in A1, and 4 in A2, A3 will display 6 without us doing anything. If we change A1 to 4, A3 will change to 8 on it’s own. You might say A3 is “observing” A1 and A2.
This is exactly how we will be constructing our system. Rather than relying on callbacks, we will “observe” values we need and will use them to produce 1 or more outputs. Once we’ve produced all the outputs we can, we’ll render the results.
An example is worth a 1000 words
Naturally this is a fairly convoluted way to add 2 numbers together but we’re doing it for illustrative purposes. Let’s walk through the code.
Line 3 imports frhttp and calls the createServer method. This returns us a server object that we can configure with GET, POST, PUT and DELETE routes.
Line 5 tells the server object to find or make a GET route. The parts of the route that start with a colon (:) indicate url variables. Don’t worry about the ‘onValue’ for now, you can think of it conceptully as a promise.
The really interesting stuff starts at line 6. This is where we define our “cells”. Most ‘when’ blocks will contain a name for debugging purposes and 3 main parts. First, the params. This is a list of “cells” or values your function is observing. After all of these values are ready, your function will be called. Next produces lets the system know what “cells” or values you plan to “return” (not in a JavaScript return statement sense, but in a more general sense). Finally fn is your function. There are a few other options that we’ll be discussing in later posts, but that’s all we need for now.
Lines 8 and 9 say that a function called ‘decode’ be observing the URL variables and producing a1 and a2.
Line 10 defines our function. All ‘when’ functions take the same two parameters. A producer and the input your function requested. The producer exposes three methods: value, error and done. We see two of these in action in this function as we produce a1 and a2. Once we’ve produced all the values we intend to, we call done.
Lines 16 to 22 describe a function called ‘add’ that will observe a1 and a2, producing a3.
24 through 27 define our render block. Render is a bit different in that it only observes the final output of a3. As long as any ‘when’ block is executing, render will not run, even if a3 becomes available. Line 24 writes the final value and ends the request.
Line 29 starts the server listening on port 8000.
What happens when this runs?
If we run this and navigate our browser to http://localhost:8000/add/3/4 here’s what happens
- FRHTTP discovers that the request is a GET request and matches the /add/:a1/:a2 route
- FRHTTP looks for anything that can be executed with currently available data (the url variables)
- Finding ‘decode’ which needs only what it has available, it executes the function defined in the block
- ‘decode’ produces ‘a1' and ‘a2'. Once those are produced the system can run ‘add’.
- After producing the two values ‘decode’ lets the system know it’s finished.
- After running ‘add’ (which produced ‘a3') the system can’t run any more functions. It then calls render which sends ‘7' to the browser.
What’s next?
That’s it for now, in the next post I’ll describe some more of the options that the ‘when’ function can take and talk about error handling.