The power of functional programming is in the way it removes code complexity by splitting a large chunk of code into an assembly of tiny simple functions. JavaScript might not be the most pure functional language, but it has more than enough language constructs to allow quick functional programming even without using excellent libraries like lodash or Ramda.
Take a simple problem: print a given string N times, pausing between each print.
Here is a typical solution:
1 | function timedPrint(character, N, interval) { |
One can verify that this function works, each character a
is printed 1 second after the previous one.
timedPrint('a', 3, 1000);
a
a
a
done printing a 3 times
The current implementation is ok, but it mixes multiple aspects in a single function timedPrint
. I will
mark each line with one of the aspects: counting
, printing
or time
function timedPrint(character, N, interval) {
var counter = 0; // counting
var intervalId = setInterval(function () { // time
console.log(character); // printing
counter += 1; // counting
if (counter >= N) { // counting
clearInterval(intervalId); // time
console.log('done printing', character, N, 'times');
}
}, interval); // time
}
This simple example does not include all possible aspects, for example I often perform argument checks at the start of each function, a practice I call paranoid programming.
Separate printing
JavaScript has the main feature of any functional language: functions are first class objects, they can be
passed as arguments to and returned from other functions. Let us separate the first aspect from the main function -
we will move the printing feature into a separate function. We will also pass the function to the timedPrint
as
argument instead of character
.
function timedPrint(workload, N, interval) {
var counter = 0;
var intervalId = setInterval(function () {
workload();
counter += 1;
if (counter >= N) {
clearInterval(intervalId);
console.log('done printing', N, 'times');
}
}, interval);
}
timedPrint(function print() { // 1
console.log('a');
}, 5, 1000);
Notice how the timedPrint
is no longer coupled to the printing operation or character
argument.
Instead it just executes the given workload
function.
We constructed the print
function on the fly using functional expression in line // 1
.
While this is ok, we can make it more obvious using another built-in JavaScript feature: partial application.
Instead of creating an entire function just to call console.log('a');
we can create a function on the
fly using Function.prototype.bind
method.
var printA = console.log.bind(console, 'a');
timedPrint(printA, 5, 1000);
At this point it makes sense to rename timedPrint
to timed
because the printing is no longer hardcoded
inside the function.
function timed(workload, N, interval) {
...
}
timed(printA, 5, 1000);
Because timed
function takes another function as an argument, timed
is now a higher-order function.
Separate the counting aspect
We are executing the given workload function N times. Can we remove the counting
aspect from the timed
function?
Easily: instead of counting inside timed
, let us pass another function that tells us when the interval should
stop. The counting can be implemented inside that function.
function timed(workload, isDone, interval) {
var intervalId = setInterval(function () {
workload();
if (isDone()) { // 1
clearInterval(intervalId);
console.log('done executing workload');
}
}, interval);
}
var printA = console.log.bind(console, 'a');
function nTimes(N) {
var counter = 0;
return function () { // 2
counter += 1;
return counter >= N;
};
}
timed(printA, nTimes(5), 1000);
The termination logic is implemented in nTimes
function using 2 JavaScript features:
- It returns another anonymous function (line
// 2
) to be called fromtimed
(line// 1
) to determine if we have executedworkload()
as many times as necessary. nTimes
keeps N parameter available to be compared tocounter
via closure. See my explanation of JavaScript closures.
The remaining timed
function is much simpler because it only deals with the timer interval logic.
It is also more powerful because we can pass different workload
and isDone
functions. For example,
let us print a string again every second for 10 seconds.
1 | function nSeconds(N) { |
In general any place you pass a primitive value and then use it for some unrelated aspect (like passing a character and then printing it), you probably want to pass a function that encapsulates both the primitive value and the logic.
Exposing the desired API function to the users
What if my users do not care about the full power that the rewritten timed
allows? What if they only care
about having the original timedPrint
API function? The functional programming makes it simple to assemble
any desired public API on the fly from the individual pieces. For example, using CommonJS module definition (for Node)
1 | function timed(workload, isDone, interval) { |
We can call the assembled timedPrint
function inside timed-print.js
file an example of functional composition -
a larger function is assembled from smaller ones.
Defending a public API function
So far we have not used any 3rd party libraries, just plain JavaScript. I will show only a single additional library that can be used to quickly check passed arguments. Again, this is an example of functional composition because it combines the individual predicate functions and the original function to return a new higher-order function.
1 | // timed and nTimes functions as before |
The defense is done using check.defend method from check-more-types
library. If we pass valid arguments everything is working correctly. If we try to pass invalid arguments,
for example a string where N
is expected, we will get an error.
// index.js
var timedPrint = require('./timed-print');
timedPrint('a', 'three', 1000);
// output
Error: Argument 2: three does not pass predicate: number of times to print should be positive
Conclusion
Plain JavaScript allows writing small, simple to understand and test, composeable functions. It has all the features necessary to create expressive and robust code using only a couple of language features: first order functions, closures, partial argument binding. To learn more read my other blog posts on functional JavaScript, and check out the excellent presentation Functional Programming by Scott Sauyet.