An Introduction to Reasonably Pure Functional Programming
When learning to program you’re first introduced to procedural programming; this is where you control a machine by feeding it a sequential list of commands. After you have an understanding of a few language fundamentals like variables, assignment, functions and objects you can cobble together a program that achieves what you set out for it do – and you feel like an absolute wizard.
The process of becoming a better programmer is all about gaining a greater ability to control the programs you write and finding the simplest solution that’s both correct and the most readable. As you become a better programmer you’ll write smaller functions, achieve better re-use of your code, write tests for your code and you’ll gain confidence that the programs you write will continue to do as you intend. No one enjoys finding and fixing bugs in code, so becoming a better programmer is also about avoiding certain things that are error-prone. Learning what to avoid comes through experience or heeding the advice of those more experienced, like Douglas Crockford famously explains in JavaScript: The Good Parts.
Functional programming gives us ways to lower the complexity of our programs by reducing them into their simplest forms: functions that behave like pure mathematical functions. Learning the principles of functional programming is a great addition to your skill set and will help you write simpler programs with fewer bugs.
The key concepts of functional programming are pure functions, immutable values, composition and taming side-effects.
Pure Functions
A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect.
// pure
function add(a, b) {
return a + b;
}
This function is pure. It doesn’t depend on or change any state outside of the function and it will always return the same output value for the same input.
// impure
var minimum = 21;
var checkAge = function(age) {
return age >= minimum; // if minimum is changed we're cactus
};
This function is impure as it relies on external mutable state outside of the function.
If we move this variable inside of the function it becomes pure and we can be certain that our function will correctly check our age every time.
// pure
var checkAge = function(age) {
var minimum = 21;
return age >= minimum;
};
Pure functions have no side-effects. Here are a few important ones to keep in mind:
- Accessing system state outside of the function
- Mutating objects passed as arguments
- Making a HTTP call
- Obtaining user input
- Querying the DOM
Controlled Mutation
You need to be aware of Mutator methods on Arrays and Objects which change the underling objects, an example of this is the difference between Array’s splice
and slice
methods.
// impure, splice mutates the array
var firstThree = function(arr) {
return arr.splice(0,3); // arr may never be the same again
};
// pure, slice returns a new array
var firstThree = function(arr) {
return arr.slice(0,3);
};
If we avoid mutating methods on objects passed to our functions our program becomes easier to reason about, we can reasonably expect our functions not to be switching things out from under us.
let items = ['a','b','c'];
let newItems = pure(items);
// I expect items to be ['a','b','c']
Benefits of Pure Functions
Pure functions have a few benefits over their impure counterparts:
- More easily testable as their sole responsibility is to map input -> output
- Results are cacheable as the same input always yields the same output
- Self documenting as the function’s dependencies are explicit
- Easier to work with as you don’t need to worry about side-effects
Because the results of pure functions are cacheable we can memoize them so expensive operations are only performed the first time the functions are called. For example, memoizing the results of searching a large index would yield big performance improvements on re-runs.
Unreasonably Pure Functional Programming
Reducing our programs down to pure functions can drastically reduce the complexity of our programs. However, our functional programs can also end up requiring Rain Man’s assistance to comprehend if we push functional abstraction too far.
import _ from 'ramda';
import $ from 'jquery';
var Impure = {
getJSON: _.curry(function(callback, url) {
$.getJSON(url, callback);
}),
setHtml: _.curry(function(sel, html) {
$(sel).html(html);
})
};
var img = function (url) {
return $('<img />', { src: url });
};
var url = function (t) {
return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' +
t + '&format=json&jsoncallback=?';
};
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var mediaToImg = _.compose(img, mediaUrl);
var images = _.compose(_.map(mediaToImg), _.prop('items'));
var renderImages = _.compose(Impure.setHtml("body"), images);
var app = _.compose(Impure.getJSON(renderImages), url);
app("cats");
Take a minute to digest the code above.
Unless you have a background in functional programming these abstractions (curry, excessive use of compose and prop) are really difficult to follow, as is the flow of execution. The code below is easier to understand and to modify, it also much more clearly describes the program than the purely functional approach above and it’s less code.
Continue reading %An Introduction to Reasonably Pure Functional Programming%
LEAVE A REPLY
You must be logged in to post a comment.