A Practical Introduction To Functional Programming (Javascript)

This is a gist of A practical introduction to functional programming by Mary Rose Cook, with code examples in Javascript instead of Python.

  • The first section of the article takes short, data-transforming loops and translates them into functional maps and reduces.
  • The second section takes longer loops, breaks them up into units, and makes each unit functional.
  • The third section takes a loop that is a long series of successive data transformations and decomposes it into a functional pipeline.

A guide rope

Functional code is characterized by one thing: the absence of side effects. It doesn’t rely on data outside the current function, and it doesn’t change data that exists outside the current function. Every other “functional” thing can be derived from this property. Use it as a guide rope as you learn.

This is a non-functional function:

var a = 0;
function increment1() {
return a += 1;
}

This is a functional function:

increment2(a) {
return a + 1;
}

Don’t iterate over lists. Use map and reduce.

Map

A Map takes a function and a collection of items. It makes a new, empty collection, runs the function on each item in the original collection, and inserts each return value into the new collection. It then returns the new collection.

The non-functional code below takes a list of real names and replaces them with randomly assigned code names.

var names = [“Mary”, “Isla”, “Sam”];
var code_names = [“Mr. Pink”, “Mr. Orange”, “Mr. Blonde”];
for(var i in code_names) {
var randomIndex = Math.floor(Math.random() * code_names.length);
var randomValue = code_names[randomIndex];
names[i] = randomValue;
}
console.log(names);
# => ["Mr. Blonde", "Mr. Pink", "Mr. Pink"]

This can be rewritten as a map:

var names = [“Mary”, “Isla”, “Sam”];
var code_names = [“Mr. Pink”, “Mr. Orange”, “Mr. Blonde”];
names = names.map(function(item) {
var randomIndex = Math.floor(Math.random() * code_names.length);
var randomValue = code_names[randomIndex];
return randomValue;
});
console.log(names);
# => ["Mr. Orange", "Mr. Orange", "Mr. Blonde"]

Reduce

Reduce takes a function and a collection of items. It returns a value that is created by combining the items.

This code counts how often the word ‘Sam’ appears in a list of strings:

var sentences = [
‘Mary read a story to Sam and Isla’,
‘Isla cuddled Sam’,
‘Sam chortled’
];

var sam_count = 0;
for(var i in sentences) {
var results = sentences[i].match(/Sam/g);
if(results) {
sam_count += results.length;
}
}

console.log(sam_count);
# => 3

This is the same code written as a reduce:

var sentences = [
‘Mary read a story to Sam and Isla’,
‘Isla cuddled Sam’,
‘Sam chortled’
];
var sam_count = sentences.reduce(
function(previousValue, currentValue) {
var results = currentValue.match(/Sam/g);
if(results) {
previousValue += results.length;
}
return previousValue;
},
0
);
console.log(sam_count);
# => 3

Why are map and reduce better?

  • They are often one-liners.
  • The important parts of the iteration — the collection, the operation and the return value — are always in the same places in every map and reduce.
  • The code in a loop may affect variables defined before it or code that runs after it. By convention, maps and reduces are functional.
  • Map and reduce are elemental operations. Every time a person reads a for loop, they have to work through the logic line by line. There are few structural regularities they can use to create a scaffolding on which to hang their understanding of the code. In contrast, map and reduce are at once building blocks that can be combined into complex algorithms, and elements that the code reader can instantly understand and abstract in their mind.
  • Map and reduce have many friends that provide useful, tweaked versions of their basic behavior. For example: filter, all, any and find.

Write declaratively, not imperatively

The program below runs a race between three cars. At each time step, each car may move forwards or it may stall. At each time step, the program prints out the paths of the cars so far. After five time steps, the race is over.

This is some sample output:

-
--
--

--
--
---

---
--
---

----
---
----

----
----
-----

This is the program:

var time = 5;
var car_positions = [1, 1, 1];
while(time > 0) {
time -= 1;
console.log(‘’);
  var carsCount = this.car_positions.length;
for(var i=0; i<carsCount; i++) {
if(Math.random() > 0.3) {
this.car_positions[i] += 1;
}

var output_str = '- '.repeat(car_position);
console.log(output_str);
}
}

The code is written imperatively. A functional version would be declarative. It would describe what to do, rather than how to do it.

Use functions

A program can be made more declarative by bundling pieces of the code into functions.

function move_cars() {
var carsCount = car_positions.length;
for(var i=0; i<carsCount; i++) {
if(Math.random() > 0.3) {
car_positions[i] += 1;
}
}
}
function draw_car(car_position) {
var output_str = '- '.repeat(car_position);
console.log(output_str);
}
function run_step_of_race() {
this.time -= 1;
move_cars();
}
function draw() {
console.log(‘’);
for(var i in car_positions) {
draw_car(car_positions[i]);
}
}
while(time > 0) {
run_step_of_race();
draw();
}

To understand this program, the reader just reads the main loop. “If there is time left, run a step of the race and draw. Check the time again.” If the reader wants to understand more about what it means to run a step of the race, or draw, they can read the code in those functions.

There are no comments any more. The code describes itself.

Splitting code into functions is a great, low brain power way to make code more readable.

This technique uses functions, but it uses them as sub-routines. They parcel up code. The code is not functional in the sense of the guide rope. The functions in the code use state that was not passed as arguments. They affect the code around them by changing external variables, rather than by returning values. To check what a function really does, the reader must read each line carefully. If they find an external variable, they must find its origin. They must see what other functions change that variable.

Remove state

This is a functional version of the car race code:

function move_cars(car_positions) {
return car_positions.map(function(item) {
if(Math.random() > 0.3) {
item += 1;
}
return item;
});
}
function draw_car(car_position) {
var output_str = ‘- ‘.repeat(car_position);
console.log(output_str);
}
function run_step_of_race(state) {
state[‘time’] -= 1;
state[‘car_positions’] = move_cars(state[‘car_positions’]);
return state;
}
function draw(state) {
console.log(‘’);
state[‘car_positions’].map(function(item) {
draw_car(item);
return item;
});
}
function race(state) {
draw(state);
if(state[‘time’] > 0) {
state = run_step_of_race(state);
race(state);
}
}
race({
‘time’: 5,
‘car_positions’: [1, 1, 1]
});

The code is still split into functions, but the functions are functional. There are three signs of this:

  • First, there are no longer any shared variables. The time and car_positions get passed straight into race().
  • Second, functions take parameters.
  • Third, no variables are instantiated inside functions. All data changes are done with return values. race() recurses with the result of run_step_of_race(). Each time a step generates a new state, it is passed immediately into the next step.

Use pipelines

In the previous section, some imperative loops were rewritten as recursions that called out to auxiliary functions. In this section, a different type of imperative loop will be rewritten using a technique called a pipeline.

The loop below performs transformations on dictionaries that hold the name, incorrect country of origin and active status of some bands.

var bands = [
{‘name’: ‘sunset rubdown’, ‘country’: ‘UK’, ‘active’: false},
{‘name’: ‘women’, ‘country’: ‘Germany’, ‘active’: false},
{‘name’: ‘a silver mt. zion’, ‘country’: ‘Spain’, ‘active’: true}
];
function format_bands(bands) {
for (var i in bands) {
bands[i][‘country’] = ‘Canada’;
var name = bands[i][‘name’];
name = name.replace(‘.’, ‘’);
var nameParts = name.split(‘ ‘);
for(var j in nameParts) {
nameParts[j] = nameParts[j].charAt(0).toUpperCase() + nameParts[j].slice(1);
}
bands[i][‘name’] = nameParts.join(“ “);
}
}

format_bands(bands);
//print bands
console.log(JSON.stringify(bands));

Worries are stirred by the name of the function. “format” is very vague. Upon closer inspection of the code, these worries begin to claw. Three things happen in the same loop. The ‘country’ key gets set to ‘Canada’. Punctuation is removed from the band name. The band name gets capitalized. It is hard to tell what the code is intended to do and hard to tell if it does what it appears to do. The code is hard to reuse, hard to test and hard to parallelize.

Compare it with this:

pipeline_each(
bands,
[set_canada_as_country, strip_punctuation_from_name, capitalize_names]
)

This code is easy to understand. It gives the impression that the auxiliary functions are functional because they seem to be chained together. The output from the previous one comprises the input to the next. If they are functional, they are easy to verify. They are also easy to reuse, easy to test and easy to parallelize.

The job of pipeline_each() is to pass the bands, one at a time, to a transformation function, like set_canada_as_country(). After the function has been applied to all the bands, pipeline_each() bundles up the transformed bands. Then, it passes each one to the next function.

Let’s look at the transformation functions.

var set_canada_as_country = function set_canada_as_country(band) {
band['country'] = "Canada";
return band;
}
var strip_punctuation_from_name = function strip_punctuation_from_name(band) {
band['name'] = band['name'].replace('.', '');
return band;
}
var capitalize_names = function capitalize_names(band) {
var nameParts = band['name'].split(‘ ‘);
for(var j in nameParts) {
nameParts[j] = nameParts[j].charAt(0).toUpperCase() + nameParts[j].slice(1);
}
band['name'] = nameParts.join(“ “);
return band;
}

pipeline_each implementation:

function pipeline_each(data, functions) {
return functions.reduce(
function(newData, currentFunction) {
return newData.map(function(item) {
return currentFunction.call(this, item);
});
},
data
);
}
bands = pipeline_each(
bands,
[set_canada_as_country, strip_punctuation_from_name, capitalize_names]
);
console.log(JSON.stringify(bands));

Conclusion

Functional programming lets you specify the ‘what’ and not the ‘how’. It helps make the code a set of clean abstractions that, if required, can be easily optimized later.