Why FP?
How functional thinking can help you write better software
What is Functional Programming
At its most basic, functional programming is a paradigm that allows functions to be treated as inputs to and outputs from other functions, so called "higher-order" functions.
Higher Order Functions
Higher order functions are functions that accept other functions as input, or generate other functions as output.
You're probably already using them.
setTimeout(function() { console.log("So this is it?") }, 1000);
setTimeout(function() { console.log("Yep") }, 2000);
Concepts
In no particular order:
- Higher Order Functions
- Purity
- Laziness
Higher Order Functions
var auditTrail = [
{timestamp: 1, transactionValue: 10},
{timestamp: 2, transactionValue: 1},
{timestamp: 30, transactionValue: 2000},
]
var imperativeGetAllByTimestamp = function(timestamps) {
var results = [];
for(var i = 0; i < db.length; i++) {
if(timestamps.indexOf(auditTrail[i].timestamp) > -1) {
results.push(db[i]);
}
}
return results;
};
var functionalGetAllByTimestamp = function(timestamps) {
return auditTrail.filter(
function(item) {
return (timestamps.indexOf(item.timestamp) > -1);
});
};
Can lead to more concise code
Immutable data structures
Objects whose state cannot be changed after creation. This helps mitigate against bugs.
Immutable data structures
var auditTrail = [
{timestamp: 1, transactionValue: 10},
{timestamp: 2, transactionValue: 244},
{timestamp: 30, transactionValue: 300},
];
var getTransactionsByTimestamp = function(timestamp) {
return auditTrail.filter(
function(item) {
return (timestamps.indexOf(item.timestamp) > -1);
});
};
var otherFunctionDefinedElsewhere() {
// Do useful stuff
auditTrail[2].transactionValue = 10;
// Do other useful stuff
}
var transactions = getTransactionsByTimestamp([1, 30])
.map(function(item) { return item.id; });
console.log(transactions);
>>> [10, 10];
Immutable data structures
var auditTrail = Immutable.List([
{timestamp: 1, transactionValue: 10},
{timestamp: 2, transactionValue: 244},
{timestamp: 30, transactionValue: 300},
]);
var getTransactionsByTimestamp = function(timestamp) {
return auditTrail.filter(
function(item) {
return (timestamps.indexOf(item.timestamp) > -1);
});
};
var otherFunctionDefinedElsewhere() {
// Do useful stuff
auditTrail[2].transactionValue = 10;
// Do other useful stuff
}
var transactions = getTransactionsByTimestamp([1, 30])
.map(function(item) { return item.id; });
console.log(transactions);
>>> [10, 300];
Pure Functions
Pure functions produce the same result whenever they're run against the same arguments.
A related concept is that of referential transparency, where an expression can be replaced with its value without affecting the behaviour of the program.
Taken together these yield interesting performance optimizations.
Pure Functions
var auditTrail = Immutable.List([
{timestamp: 1, transactionValue: 10},
{timestamp: 2, transactionValue: 244},
...
{timestamp: 30, transactionValue: 300},
]);
var getTransactionsByTimestamp = function(timestamp, auditTrail) {
// We now pass the auditTrail as a parameter since purity relies on
// all state being explicit
return auditTrail.filter(
function(item) {
return (timestamps.indexOf(item.timestamp) > -1);
});
};
Pure Functions
Since we've transformed getTransactionsByTimestamp into a pure function, we know that given the same audit trail, and the same list of timestamps, the result will always be the same. We can take advantage of this fact to memoize the function.
Pure Functions
var auditTrail = Immutable.List([
{timestamp: 1, transactionValue: 10},
{timestamp: 2, transactionValue: 244},
...
{timestamp: 30, transactionValue: 300}
]);
var getTransactionsByTimestamp = function(timestamp, auditTrail) {
// We now pass the auditTrail as a parameter since purity relies on
// all state being explicit
// Performance would be O(n) (linear time) because of the call to filter()
return auditTrail.filter(
function(item) {
return (timestamps.indexOf(item.timestamp) > -1);
});
};
var memoize = function (fn) {
var cache = {};
return function() {
var args = arguments.slice();
if (args in cache)
return cache[args];
else
return (cache[args] = fn.apply(this, args));
}
};
var memoizedGetTransactionsByTimestamp = memoize(getTransactionByTimestamp);
// Initial performance is O(n), but subsequently O(1) - constant time, for the
// same set of arguments.
Pure Functions
A further optimization would be to replace calls to the function with the actual return value during a pre-processing/compilation step. We can do this confidently since we know the value won't change during runtime, saving us even more computation cycles.
Lazy Evaluation
Lazy evaluation is a feature of functional programming that enables holding off on evaluating an expression until the value is needed, with interesting results. e.g. operations on an infinitely long list are possible, since we only take the elements that are needed.
Lazy Evaluation
Lazy evaluation is a feature of functional programming that enables holding off on evaluating an expression until the value is needed, with interesting results. e.g. operations on an infinitely long list are possible, since we only take the elements that are needed. There are several implementations of lazy lists in JS. We'll go with streamjs.org for the purposes of this demonstration.
Lazy Evaluation
var auditTrail = Stream(
{timestamp: 1, transactionValue: 10},
{timestamp: 2, transactionValue: 244},
...
{timestamp: 3000000, transactionValue: 23}
);
var getTransactionsByTimestamp = function(timestamps, auditTrail) {
return auditTrail.filter(
function(item) {
return (timestamps.indexOf(item.timestamp) > -1);
});
};
var transactionsBetween2000And3000 = getTransactionsByTimestamp(
Stream.range(2000, 3000),
auditTrail);
// The actual filtered set is not generated until it is called, as would be the case with
// a standard array. This means that auditTrail can hold far more items than a standard array,
// and allows us
transactionsBetween2000And3000.map(doSomethingWithEachItem());
Further Reading
- Why Functional Programming Matters:
https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf - Pure Functions:
http://www.sitepoint.com/functional-programming-pure-functions/ - Immutable JS:
http://facebook.github.io/immutable-js/docs/#/ - Stream JS: http://streamjs.org/
Why Functional Programming
By Okal Otieno
Why Functional Programming
- 1,326