Object-oriented programming and procedural programming are both programming paradigms, and functional programming is also a programming paradigm, meaning that they are both ways of thinking about software building. Functional code tends to be cleaner, more predictable, and easier to test than imperative or object-oriented code.
What is Functional Programming (often called FP for short)
The process of building software by combining pure functions that avoid shared state, volatile data, and side effects. Functional programming is declarative rather than imperative, and application state flows through pure functions.
Understand the core concepts of functional programming
- Pure functions
- Function composition
- Avoid shared state
- Avoid mutating state
- Avoid side effects
- Declarative and Imperative
Pure functions
A pure function is one that satisfies the following conditions:
- The same input always returns the same output
- No side effects
- Does not depend on external state
Examples of pure functions in JS:
String.prototype.toUpperCase
Array.prototype.map
Function.prototype.bind
Copy the code
Examples of non-pure functions in JS:
Date.now
Math.random
Array.prototype.sort
document.body.appendChild
Copy the code
Benefits of pure functions:
- Easy to test (context free)
- Parallel computing (timing independent)
- Bug self-limiting
Function of composite
Function composition is the process of combining two or more functions to produce a new function or perform some calculation.
In JavaScript it is equivalent to executing f(g(x)).
Shared state
Shared state means that any variable, object, or memory space exists in shared scopes (both global and closure scopes) or is passed between scopes as an attribute of the object.
In general, in object-oriented programming, objects are shared across scopes by adding properties to other objects. Unlike object-oriented programming, functional programming avoids sharing state and relies on immutable data structures and purely computational processes to derive new data from existing data. A common problem with shared state is changing the order of function calls. The order of function calls can change the result of a function call, which can lead to a series of errors:
const x = {
val: 2
};
const x1 = (a)= > x.val += 1;
const x2 = (a)= > x.val *= 2;
x1(); / / - > 3
x2(); / / - > 6
Copy the code
The following example is the same as above, except for the order of the function calls:
const x = {
val: 2
};
const x1 = (a)= > x.val += 1;
const x2 = (a)= > x.val *= 2;
x2(); / / - > 4
x1(); / / - > 5
Copy the code
If you avoid sharing state, you won’t change the contents of a function, or changing the timing of a function call won’t spread and break the rest of the program:
const x = {
val: 2
};
const x1 = x= > Object.assign({}, x, { val: x.val + 1});
const x2 = x= > Object.assign({}, x, { val: x.val * 2});
x1(x); / / - > 3
x2(x); / / - > 4
/** x2(x); // -> 4 x1(x); / / - > 3 * /
Copy the code
Do not modify the status
In other languages, variables are often used to hold “state”. Functional programming, on the other hand, simply returns new values and does not modify system variables, which is a non-destructive data conversion.
Side effects
Side effects are any changes in application state observed outside of a function call, other than the return value of the function.
Side effects include:
- Changes any external variables or object properties
- Write files
- Send a network request
- Output on screen
- Call another function that has side effects
In functional programming, side effects are avoided as much as possible.
Declarative and imperative
- Imperative: A program spends a lot of code describing the specific steps used to achieve the desired result, “How to do”
- Declarative: The program abstracts the control flow process and spends a lot of code describing the data flow, i.e., “What to do”
Functional programming is a declarative paradigm, meaning that program logic does not need to be expressed by explicitly describing the flow of control. Command:
let list = [1.2.3.4];
let map1 = [];
for (let i = 0; i < list.length; i++) {
map1.push(list[i] * 2);
}
Copy the code
Declarative:
let list = [1.2.3.4];
let map2 = list.map(x= > 2 * x);
Copy the code
Let’s look at two important concepts that emerge from the declarative example:
Concepts, things, objects, etc., which have some relation to each other, constitute categories.
The mathematical model of category is simply “set + function”.
- Container: Think of a category as a Container that contains: values and value distortions (functions)
- Functor: a container with an interface that lets you iterate over its values. The ability to map every value in a container to another container.
The application of functional programming
In functional programming, it is common to use functors and use higher-order function abstractions to create generic functional functions to handle arbitrary numeric values or different types of data.
Higher-order functions
A higher-order function is a function that takes a function as an argument, returns a function as a value, or both.
Higher-order functions are often used to:
- Part applies to function parameters (partial function application) or creates a curryized function for reuse or function composition.
- Takes a list of functions and returns some composition of functions from the list. Object-oriented programming tends to concentrate methods and data on objects. Those methods that are concentrated can usually only be used to manipulate data contained on a particular object instance. Functional programming, on the other hand, tends to reuse a common set of functions to process data.
Partial function
The form that produces a new custom function by specifying partial parameters is a partial function.
const isType = function (type) {
return function (obj) {
return toString.call(obj) == '[object' + type + '] ';
};
};
const isString = isType('string');
const isFunction = isType('function');
Copy the code
Currie,
Currying is the conversion of a multi-parameter function into multiple single-parameter functions.
// Before currying
function add(x, y) {
return x + y;
}
add(1.2) / / 3
// After currying
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2) (1) / / 3
Copy the code
Composition of functions
If a value has to pass through multiple functions to become another value, you can combine all the intermediate steps into a single function. This is called “composition of functions.”
An example of a simple function composition:
const compose = function (f, g) {
return function (x) {
return f(g(x));
};
}
Copy the code
Implement a higher-order function to reduce impure functions:
function batch (fn) {
return function (target, ... args) {
if (target.length >= 0) {
return Array.from(target).map(item= > fn.apply(this, [item, ...args]));
} else {
return fn.apply(this, [target, ...args]); }}}Copy the code
For example: two impure functions -> batch(fn) -> one impure function
conclusion
Functional programming preferences:
- Replace statements with expressions
- Make mutable data immutable
- Replace command control flow with function composition
- Use declarative rather than imperative code
- Use pure functions instead of shared state and side effects
- Use containers and higher-order functions instead of polymorphisms
- Use higher-order functions to manipulate many data types, creating generic, reusable functionality instead of just manipulating data in a set