Functional programming is a way of writing clearner code through clever way of mutating, combinding, and using functions.
Arrow functions create a concise expression that encapsulates a small piece of functionality. Additionally, arrows retain the scope of the caller inside the function eliminating the need of self = this
.
For example:
// const multiply = function(x, y) { // return x * y; // } // Can be rewritten as: // const multiply = (x, y) => { return x * y }; // Since the function is a single expression return and braces are not needed: const multiply = (x, y) => x * y; console.log(multiply(5,10)) // 50
- Global scope
- Local scope
- Function scope
- Block scope
- Function hoisting and scope
- Functions do not have access to each other's scope
- Nested scope
Global scope
If a variable is declared outside all functions or curly braces ({})
, it is said to be defined in the global scope.
const globalVariable = 'some value';
Clousure is the functions that have access to the parent scope, even when the parent function has closed.
For every closure there are 3 scopes:
- Local scope ( Own scope)
- Outer functions scope
- Global scope
Function delegates encapsulate a method allowing functions to be composed or passed as data.
For example:
const isZero = n => n === 0; const a = [0, 1, 0, 3, 4, 0]; console.log(a.filter(isZero)); // [0, 0, 0]
Statements define an action and are executed for their side effect. Expressions produce a result without mutating state.
For example:
Statement
const getSalutation = function(hour) { var salutation; // temp value if (hour < 12) { salutation = "Good Morning"; } else { salutation = "Good Afternoon" } return salutation; // mutated value } console.log(getSalutation(10)); // Good Morning
Expression
const getSalutation = (hour) => hour < 12 ? "Good Morning" : "Good Afternoon"; console.log(getSalutation(10)); // Good Morning
A function that accepts another function as a parameter, or returns another function.
For example:
function mapConsecutive(values, fn) { let result = []; for(let i=0; i < values.length -1; i++) { result.push(fn(values[i], values[i+1])); } return result; } const letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']; let twoByTwo = mapConsecutive(letters, (x, y) => [x, y]); console.log(twoByTwo); // [[a, b], [b, c], [c, d], [d, e], [e, f], [f, g]]
A function that given the same input, will always return the same output. A pure function produces no side effects.
Note: Side effects may include:
- changing the file system
- inserting a record into a database
- making an http call
- mutations
- printing to the screen / logging
- obtaining user input
- querying the DOM
- accessing system state
For example:
var xs = [1, 2, 3, 4, 5]; // ==== pure ==== xs.slice(0, 3); //=> [1, 2, 3] xs.slice(0, 3); //=> [1, 2, 3] xs.slice(0, 3); //=> [1, 2, 3] // ==== pure ==== xs.splice(0, 3); //=> [1, 2, 3] xs.splice(0, 3); //=> [4, 5] xs.splice(0, 3); //=> [] // pure var checkAge = function(age) { var minimum = 21; return age >= minimum; }; // impure var minimum = 21; var checkAge = function(age) { return age >= minimum; };
Recursion is the ability to call a function from within itself.
For example:
function countUp(n) { console.log(n); if(n < 10) countUp(n+1); } countUp(1);
Anonymous function is the function that was declared without any named identifier to refer to it.
For example:
// Normal function function sayHi() { alert('Hello world!'); } sayHi(); // Anonymous function assigned to sayHi variable var sayHi = function() { alert('Hello World!'); } // Use as an argument to other functions setTimeout(function() { console.log('Hello World!'); }, 1000); // Use as a closure (function() { alert('Hello World!'); })(); // Use as a closure with parameters (function(msg) { alert(msg); }('Hello World!')); // An anonymous function can refer to itself via arguments.callee local variable, useful for recursive anonymous functions console.log( (function(n) { return (1 < n) ? arguments.callee(n - 1) + n : 1; })(10) ); // Instead of using arguments.callee, you can use named function expression instead console.log( (function sum(n) { return (n <= 1) ? 1 : sum(n-1) + n })(10) );
Currying allows a function with multiple arguments to be translated into a sequence of functions. Curried functions can be tailored to match the signature of another function.
// Statement // function convertUnits(toUnit = 0, factor, offset) { // return function(input) { // return ((offset + input) * factor).toFixed(2).concat(toUnit) // } // } // Can be written as an expression const convertUnits = (toUnit, factor, offset = 0) => input => ((offset + input) * factor).toFixed(2).concat(toUnit); const milesToKm = convertUnits('km', 1.60936, 0); const poundsToKg = convertUnits('kg', 0.45460, 0); const farenheitToCelsius = convertUnits('degrees C', 0.5556, -32); console.log(milesToKm(10)); //"16.09 km" console.log(poundsToKg(2.5)); //"1.14 kg" console.log(farenheitToCelsius(98)); //"36.67 degrees C" const weightsInPounds = [5, 15.4, 9.8, 110]; // Without currying // const weightsInKg = weightsInPounds.map(x => convertUnits('kg', 0.45460, 0)(x)); // With currying const weightsInKg = weightsInPounds.map(poundsToKg); console.log(weightsInKg); // 2.27kg, 7.00kg, 4.46kg, 50.01kg
Array Functions are the gateway to functional programming in JavaScript. These functions make short work of most imperative programming routines that work on arrays and collections.
[].every(fn)
Checks if all elements in an array pass a test.
[].some(fn) | [].includes(fn)
Checks if any of the elements in an array pass a test.
[].find(fn)
Returns the value of the first element in the array that passes a test.
[].filter(fn)
Creates an array filled with only the array elements that pass a test.
[].map(fn)
Creates a new array with the results of a function applied to every element in the array.
[].reduce(fn(accumulator, currentValue))
Executes a provided function for each value of the array (from left-to-right). Returns a single value, the accumulator.
[].sort(fn(a,b)) warning, mutates state!
Modifies an array by sorting the items within an array. An optional compare function can be used to customize sort behavior. Use the spread operator to avoid mutation. [...arr].sort()
[].reverse() warning, mutates state!
Reverses the order of the elements in an array. Use the spread operator to avoid mutation. [...arr] . reverse()
For example:
const names = [ {text: "Alpha", value: 5}, {text: "Beta", value: 2}, {text: "Gamma", value: 4}, ]; //Checks if all elements in an array pass a test. let everyResult = names.every(x => x.value > 0); //true // Checks if any of the elements in an array pass a test. let someResult = names.some(x => x.text === "Alpha"); //true // Returns the value of the first element in the array that passes a test. let findResult = names.find(x => x.text === "Gamma"); //{text: "Gamma", value: 4} // Creates an array filled with only the array elements that pass a test. let filterResult = names.filter(x => x.value > 3); //[{text: "Alpha", value: 5}, {text: "Gamma", value: 4}] // Creates a new array with the results of a function applied to every element in the array. let mapResult = names.map(x => ({...x, value: x.value *10})); //[{text: "Alpha", value: 50}, {text: "Beta", value: 20}, {text: "Gamma", value: 40}]; // Executes a provided function for each value of the array (from left-to-right). The returns a single value, the accumulator. let reduceResult = names.reduce((accumulator, currentValue) => currentValue.value > accumulator.value ? currentValue : accumulator); // Get the largest object by value: {"text":"Alpha","value":5} // Modifies an array by sorting the items within an array. An optional compare function can be used to customize sort behavior. Use the spread operator to avoid mutation. [...arr].sort() let sortResult = [...names].sort((a,b) => b.value - a.value); // reverses the order of the elements in an array. Use the spread operator to avoid mutation. [...arr].reverse() let reverseResult = [...names].reverse(); // Results const appDiv = document.getElementById('app'); appDiv.innerHTML = ` <p>every: ${everyResult}</p> <p>some: ${someResult}</p> <p>find: ${JSON.stringify(findResult)}</p> <p>filter: ${JSON.stringify(filterResult)}</p> <p>map: ${JSON.stringify(mapResult)}</p> <p>reduce: ${JSON.stringify(reduceResult)}</p> <p>reverse: ${JSON.stringify(reverseResult)}</p> <p>sort: ${JSON.stringify(sortResult)}</p>`;
Method chains allow a series of functions to operate in succession to reach a final result. Method chains allow function composition similar to a pipeline.
For example:
let cart = [ { name: "Drink", price: 3.12 }, { name: "Steak", price: 45.15}, { name: "Drink", price: 11.01} ]; let drinkTotal = cart.filter(x=> x.name === "Drink") .map(x=> x.price) .reduce((t,v) => t + v) .toFixed(2); console.log(`Total Drink Cost $${drinkTotal}`); // Total Drink Cost $14.13
A pipeline allows for easy function composition when performing multiple operations on a variable. Since JavaScript lacks a Pipeline operator, a design pattern can be used to accomplish the task.
const pipe = functions => data => { return functions.reduce( (value, func) => func(value), data ); }; let cart = [3.12, 45.15, 11.01]; const addSalesTax = (total, taxRate) => (total * taxRate) + total; const tally = orders => pipe([ x => x.reduce((total, val) => total + val), // sum the order x => addSalesTax(x, 0.09), x => `Order Total = ${x.toFixed(2)}` // convert to text ])(orders); console.log(tally(cart)); // Order Total = 64.62