Stephen Young
It's all about functions!
You probably do "functional" things every day…
var returnFunc = function(z) {
return function(a) {
return a * z;
};
};
var funcAsArgument = function(a, func) {
return func(a);
};
var newArray = [1, 2, 3].map(function(v, i) {
return v + 2;
});
console.log(newArray) // [3, 4, 5]
var func = function() {
var a = 3;
return function(b) {
return b * a;
};
};
var closure = func();
closure(4) // 12
All of these examples demonstrate one of the key features of functional programming:
Functions are values
They are first-class!
Three key concepts:
In functional programming computation is done by applying functions:
// Functional computation
var numbers = [10, 15, 20, 25, 30];
var addNumbersInArray = numbers.reduce(function(a, b) {
return a + b;
});
console.log(addNumbersInArray) // 100
// Non-functional computation
var numbers = [10, 15, 20, 25, 30];
var addNumbersInArray = 0;
for (var i = 0, ln = numbers.length; i < ln; i += 1) {
addNumbersInArray += numbers[i];
}
console.log(addNumbersInArray) // 100
Functional programming is declarative:
I do not care how you calculate the result I'm just going to give you this function and let you deal with it.
var isEveryElementInThisArrayTrue = [true, true, false].every(function(v) {
return v === true;
});
console.log(isEveryElementInThisArrayTrue) // false
Non-functional programming (or object-oriented programming) is imperative:
I will give you step-by-step instructions of how I want you to achieve the result.
var isEveryElementInThisArrayTrue = function(arrayOfBooleans) {
var result;
for (var i = 0, ln = arrayOfBooleans.length; i < ln; i += 1) {
result = arrayOfBooleans[i] === true;
}
return result;
};
console.log(isEveryElementInThisArrayTrue([true, true, false])) // false
Functional programming is about being predictable.
Every time I call a function with the same arguments I will expect to get the same result:
// Every call to 'increment' produces the same result
var number = 1;
var increment = function(n) {
return n + 1;
};
increment(number); // 2
increment(number); // 2
increment(number); // 2
This is known as referential transparency…
And can be described as a stateless approach to programming.
Object-oriented programming is stateful:
var number = 1;
var increment = function() {
return number += 1;
};
increment(); // 2
increment(); // 3
increment(); // 4
The state of the program's environment is changed.
Object-oriented programming works by changing program state to produce answers.
// To get an answer we continuously change the value of 'addNumbersInArray'
var numbers = [10, 15, 20, 25, 30];
var addNumbersInArray = 0;
for (var i = 0, ln = numbers.length; i < ln; i += 1) {
addNumbersInArray += numbers[i];
}
console.log(addNumbersInArray) // 100
Functional programming uses function application to produce answers without changing state.
var numbers = [10, 15, 20, 25, 30];
var addNumbersInArray = function(numbersArray, i, result) {
if (i >= numbersArray.length) {
return result;
} else {
return addNumbersInArray(numbersArray, i + 1, result + numbersArray[i]);
}
};
console.log(addNumbersInArray(numbers, 0, 0)); // 100
This is an example of recursion (a function that calls itself).
Functional programming is about working with data that does not change.
var x = 3;
x = 4; // Illegal with functional programming
var array = [1, 'one', 2, 'two', 3];
array.push('three'); // Illegal with functional programming
var obj = {a: 1, b: 2, c: 3};
obj.d = 4; // Illegal with functional programming
All of these mutate data and are stateful operations.
Functional programming advocates immutable data.
var x = 3;
var y = x + 1;
var array = [1, 'one', 2, 'two', 3];
var array2 = array.concat('three'); // Produces a new array and doesn't change the old one
var object = {a: 1, b: 2, c: 3};
var object2 = Object.create(obj, {d: {value: 4, enumerable: true}}; // Produces a new object and doesn't change the old one
All of these are stateless operations.
// This reads nicer
var isEveryElementInThisArrayTrue = [true, true, false].every(function(v) {
return v === true;
});
console.log(isEveryElementInThisArrayTrue) // false
// than this
var isEveryElementInThisArrayTrue = function(arrayOfBooleans) {
var result;
for (var i = 0, ln = arrayOfBooleans.length; i < ln; i += 1) {
result = arrayOfBooleans[i] === true;
}
return result;
};
console.log(isEveryElementInThisArrayTrue([true, true, false])) // false
var anyFunction = function() {
var x = 5;
var data = {
a: 5,
b: 6
};
anotherFunction(data);
x = 5 + data.a;
};
Uses continuations (functions) to represent the next stage of computation:
$.get('url', function(data) {
console.log(data);
});
http.get('url', function(data) {
console.log(data);
});
A more complex example to compute the factorial of n
.
If n
is 3 then the factorial of n
is 1 * 2 * 3 = 6
var CPSFactorial = function(n, continuation) {
if (n < 2) {
return continuation(n);
} else {
var new_continuation = function(v) {
var result = v * n;
return continuation(result);
};
return CPSFactorial(n - 1, new_continuation);
}
};
CPSFactorial(5, function(v) {
return v;
}); // 1 * 2 * 3 * 4 * 5 = 120
Pre-filling an argument to a function.
The simplest way is with bind
:
var bindExample = function(a, b) {
return a / b;
};
var test = bindExample.bind(null, 3);
test(4); // 0.75
However, we can write our own:
var bind = function(a, func) {
return function() {
return func.apply(null, [a].concat(Array.prototype.slice.call(arguments)));
};
};
var bindExample = function(a, b) {
return a / b;
};
var test = bind(3, bindExample);
test(4); // 0.75
Similar to partial application, however, …
A curried function will return a new function until it receives all of its arguments.
var curry = function(fn) {
var result = function() {
var newArgs = Array.prototype.slice.call(arguments, 0);
if (newArgs.length >= fn.length) {
var finalResult = fn.apply(null, newArgs);
return finalResult;
} else {
return Function.prototype.bind.apply(result, [null].concat(newArgs));
}
};
return result;
};
var addNumbers = curry(function(arg1, arg2, arg3) {
return arg1 + arg2 + arg3;
});
var curry1 = addNumbers(4); // function
var curry2 = curry1(5); // function
console.log(curry2(6)); // 15
var curryA = addNumbers(10, 20); // function
console.log(curryA(30)); // 60
var curryI = addNumbers(); // function
console.log(curryI(1, 2, 3)); // 6