Programming languages are classified in number of ways. One popular method for classfying is Programming paradigm.
Let's look at couple of paradigm that are relevant to current topic.
Imperative is where programmer instruct a machine how to change its state. Procedural and object oriented programming are derived from Imperative Programming.
Declarative is where a programmer only defines properties of results leaving how to compute it. Functional programming falls under this category. The desired result is declared as the value of series of function applications.
Fundamental difference between procedural, object-oriented, and functional programming.
Procedural Programming
It can be represented as assembly lines.
"Raw Input → Step 1 → Step 2 → … → Step n → Final Product.
Raw input is changed (mutated) at every step.
Each step is depend upon previous step.
There is no meaning of an individual step in isolation.
We get only one specific product. If we need a slightly different change in product, then we need new assembly line.
In terms of programming, the global mutable state (raw material is changing at every step) creates interdependencies.
Object-Oriented Programming
Same as procedural but with some modularisation. The global state is broken down into objects a part of instruction can change only a part of state. Albeit, interdependencies still exist.
Functional Programming
It takes completely different approach. Programs are formed by appying and composing functions. A programmer tries to bind everything into purely mathematical function style. Using pure function instead of procedures avoids mutable state. The main focus is on what to solve in contrast to how to solve in imperative programming.
// Imperative const list = [1, 2, 3, 4, 5]; const item = list[list.length - 1];
// Declarative const list = [1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 15, 16, 18, 20]; const item = getLastItem(list);
- assigned to a variable
- passed as an argument
- returned by another function
// Assign a function to a variable const foo = function() { console.log("foobar"); } foo(); // Invokve using variable // Pass a function as an argument function sayHello() { return "Hello, "; } function greeting(helloMessage, name) { console.log(helloMessage() + name); } greeting(sayHello, "JavaScript!"); // Pass `sayHello` as an argument to `greeting` function // Return a function function sayHello() { return function() { console.log("Hello!"); } }
Higher-Order Function
Takes a function as an argument or returns a function or both.
// Takes function as an argument - map const numbers = [1, 2, 3]; const doubledNumbers = numbers.map(number => number * 2); // [2, 4, 6] // Returns a function function sayHello() { return function() { console.log("Hello!"); } }
Immutability :
Immutable means unchangeable. Once array/object is defined, it can't be modified. To change properties of object or change element of an array, one should create a new copy of an array or an object with a changed value.
var myCar = { make: 'Ford', model: 'Mustang', year: 1969 }; myCar.model = 'Mercedes'; myCar.year= 1999; // Instead should be achieved as - const myCar = { make: 'Ford', model: 'Mustang', year: 1969 }; const updatedCar = { ...myCar , model: 'BMW', }; const latestYear = { ...updatedCar , year: 2021, };
// Get all evens const numbers = [2, 4, 5, 6, 8, 10]; numbers.splice(2, 1); console.log(numbers); // [2, 4, 6, 8, 10] // Instead should be achieved as - const numbers = [2, 4, 5, 6, 8, 10]; const evens = [...numbers.slice(0, 2), ...numbers.slice(3)]; console.log(evens); // [2, 4, 6, 8, 10]
Pure Function
A pure function is a function that given the same input will always return the same output and does not have any observable side effects.
- changing a file system
- inserting a record in a DB
- querying DOM
- mutations
// Impure const list = [1,2,3,4,5]; list.splice(0,3); // [1,2,3] list.splice(0,3); // [4,5] list.splice(0,3); // [] // Pure const list = [1,2,3,4,5]; list.slice(0,3); // [1,2,3] list.slice(0,3); // [1,2,3] list.slice(0,3); // [1,2,3]
const list = [1,2,3,4,5]; // impure const addToList = (list, number) => { list.push(number); return list; } // pure const addToList = (list, number) => { const newList = [...list]; newList.push(number); return newList; }
Currying
Currying is where you can call a function with fewer arguments than it expects. It then returns a function that takes the remaining arguments.
const discount = (price, percent) => (price * percent); discount(100, 0.1); // 10 // Currying const discount = percent => price => price * percent; const flat10Percent = discount(0.1); const flat50Percent = discount(0.5); const newYearDiscount = discount(0.7); flat10Percent(100); // 10 flat50Percent(100); // 50 newYearDiscount(100); // 70
function sum(a, b) { return a + b; } // Actual : sum(1, 2); // Expected : sum(1)(2); function curry(f) { return function(a) { return function(b) { return f(a, b); }; }; } let curriedSum = curry(sum); curriedSum(1)(2); // 3
Composition
Composition simply is to compose 2 functions to create a new function.
f(x) = x * 2 g(y) = y + 1 g.f = g(f(x)) composition: g(f(x)), for x = 2; Result = 5 f: x -> y g: y -> z h: x -> z __________________________ For x = 2: f: 2 -> 4 g: 4 -> 5 h: 2 -> 5 const f = x => x * 2; const g = y => y + 1; const compose = (g, f) => x => g(f(x)); const h = compose(g, f); h(2); // 5
Pointfree
Pointfree simply means we don’t explicitly specify what data is being passed to a function.
f(x) = g(x) // Point free f = g ------------------------------------- const numbers = [1, 2, 3, 4, 5]; const doubles = numbers.map(x => x * 2); // Making it pointfree const double = x => x * 2 const numbers = [1, 2, 3, 4, 5] const doubles = numbers.map(double); // Point free