This is the 11th day of my participation in Gwen Challenge
preface
The theoretical basis of functional programming is the lambda calculus (including a transformation rule and a function definition), which is itself a mathematical abstraction rather than a programming language. Functional programming can be thought of as a form of declarative programming that writes more like a series of declarations. The pointfree programming style is recommended to reduce meaningless intermediate variables and make code more readable.
The core features
Immutable data
All data is immutable. If you want to modify an object, create a new object to modify it, not an existing object.
stateless
For a function, given the same input, the same output must be given, independent of changes in external state.
In order to achieve the two characteristics of immutable and stateless data, functional programming requires functions to have two characteristics: no side effects and pure functions. Pure functions with no side effects are obviously referential transparent.
The basic concept
- Avoid side effects
- Pure functions
- Avoid changing states
- Avoiding shared states
- Reference transparent
1. Avoid side effects
A side effect is a change in system state during computation.
Sources of side effects:
- The configuration file
- The database
- User input
- Modifying a File System
- Sending an HTTP request
- The random number
- Reference free variable
- Console output and log printing
- Browser cookie
- .
Functional programming recommends using Monads to separate and encapsulate side effects from pure functions
2. Pure functions
Pure functions need to meet the following conditions:
- The same input always the same output
- No side effects
- Independent of external states
Native pure functions in JS: String.prototype.toUpperCase Function.prototype.bind Array.prototype.concat -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- not pure functions: Date. Now math.h random Array. The prototype. Sort the document. The body. The appendChildCopy the code
Advantages of pure functions:
- Easy to test (context-free)
- Computations can be performed in parallel (sequential independent, not deadlocked)
- Bug self-limited (does not spread, does not affect others)
- Cacheable (The same input always equals the same output, so the results can be cached)
- portable
Lodash is a typical library of pure functions. If you open node_modules, you’ll almost always see the Lodash module
3. Avoid changing your status
Functional programming requires each function to be independent, and all functions must not modify external variables. In this way, each data state is not changed. Using pure functions does not change the external state, so there are no side effects.
4. Avoid sharing
In object-oriented programming, you can see a lot of shared variables, shared memory space, etc. Functional programming requires each function to avoid shared state, because using shared state, the order in which the functions are called will cause the data in the shared state to change.
5. Transparent references
A function is reference-transparent if it does not depend on external variables or states, but only on input parameters. If we can replace the called function expression with a unique value without changing the running state of the program, then the function is reference-transparent.
A function that references transparency must be pure.
let increment = counter= > counter+1;
let sum = 2 * 3 + increment(6) + increment(5);
//increment(6), increment(5) can be uniquely replaced
Copy the code
The specific implementation
- Higher-order functions
- Currie,
- Partial function
- Function composition (pipe/composition)
Higher-order functions
Satisfy any of the following conditions:
- Take a function as an argument
- Return a function
The built-in map, filter, and Reduce functions of Array are all high-order functions. Also, such as commonly used functions, such as anti-shake throttling, can be achieved by higher-order functions
Attach throttling code:
/ / throttling
function throttle(fun, delay) {
let timer = null;
let prev = Date.now();
return function() {
let self = this;
let _args = arguments;
let now = Date.now();
let diff = now-prev;
if(timer) {
clearTimeout(timer);
}
if(diff >= delay) { // The time difference from the last execution is greater than or equal to the specified time value
fun.apply(self,_args);
prev = now;
} else { // The method is guaranteed to fire once after the event
timer = setTimeout(function(){
fun.apply(self, _args);
prev = Date.now();
timer = null; }, delay-diff); }}}let throttleFun = throttle(opFun, 1000);
Copy the code
Higher-order functions can be used to abstract general problems. At the same time, abstraction helps us mask the details and focus only on our goals. The map function, for example, can map objects, strings, numbers, any data type, because it takes a function as an argument to handle various data types.
Currie,
Kerrization, also known as partial evaluation, can transform a function of several variables into a function of one variable, transform a function that takes multiple parameters into a function that takes a single parameter, and then return a new function that takes the remaining parameters. The biggest feature is the continuous accumulation of incoming parameters, delayed execution, increasing the applicability of the function.
The function.prototype. bind method is a typical Currified Function that sets the first parameter to the execution context and passes the rest to the calling method.
Advantages of Currization:
- Lazy calculation/running (lazy evaluation)
- Parameters of reuse
- Return early
- Multivariate functions can be converted into unary functions, which can be combined with functions to produce powerful functions
// Parameter multiplexing
function curryCheck(reg) {
return function(txt) {
returnreg.test(txt); }}let hasNumber = curryCheck(/\d+/g);
let hasString = curryCheck(/[a-zA-Z]+/g)
hasNumber('sfksfjl')
hasString('sfsfs')
// Return early
const addEvent = (function(){
if (window.addEventListener) {
return function (type, el, fn, capture) { el.addEventListener(type, fn, capture); }}else if(window.attachEvent){
return function (type, el, fn) {
el.attachEvent('on'+ type, fn); }}}) ();Copy the code
A simple example of currization:
// General implementation
function add(a,b) {
return a+b;
}
// Closure functions are currified
function add(a) {
return function(b) { a+b; }}// The arrow function is currified
const add = x= > y= > x+y;
const f = add(1);
f(2) / / 3;
Copy the code
Look at a classic question:
Write a sum function to achieve the following functions: sum (1) (2) (3) (4) / / 10 sum (1) (2) (3)... (n)Copy the code
sum(1)(2)(3)
function curry(fun) {
return function curried() {
let args = Array.prototype.slice.call(arguments);
if(arguments.length < fun.length) { // Continue to input
return function() {
let innerArgs = Array.prototype.slice.call(arguments);
let allParams = args.concat(innerArgs);
return curried.apply(this, allParams); }}else {
return fun.apply(this, args); }}}Copy the code
sum(1)(2).. (n)
function add (. args) {
return args.reduce((a, b) = > a + b)
}
function curry (fn) {
let args = [] Fn. length cannot process the rest of the arguments, including only the number of arguments before the first one has a default value
return function curried (. newArgs) {
if (newArgs.length) {
args = [
...args,
...newArgs
]
return curried;
} else {
let val = fn.apply(this, args)
args = [] // Ensure that it is cleared when called again
return val
}
}
}
let addCurry = curry(add)
console.log(addCurry(1) (2) (4) (7) (8) (9) ())Copy the code
Tip: The length of a function is the number of its parameters, not the rest of the parameters, only the number of parameters before the first one has a default value. For example, fn = (a, b, C, d, e) => {} then fn. Length =5
Partial function
Fixed one or more arguments to a function that returns a new function to accept the remaining variable arguments.
It looks like partial functions are very similar to Currie functions, one is converted into n unary functions, and one is converted into an n-x meta-function. To cite functional-Progamming-Context, the relationship is as follows:
Curried functions are automatically partially applied.
Copy the code
Function composition
If a function needs to be processed by multiple functions to get the final result, the intermediate functions can be combined into a single function, called function combination. A function is like a pipeline of data, and a combination of functions connects these pipes, allowing data to pass through multiple pipes to form the final result.
Features of function combination:
- Function combinations are executed from right to left by default
- Each of the combined functions must be pure
Examples of function combinations:
const compose = f= > g= > x= > f(g(x));
const f = compose (x= > x * 4) (x= > x + 3);
f(2) / / 20
Copy the code
For compose, the rest parameter is not fixed.
const compose = (. fns) = > {
return (. args) = > {
let res = args;
console.log('-- -- -- -- -- -- -- -- -- -... The args data ',res);
for (let i = fns.length - 1; i > -1; i--) {
console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- --',fns[i], res)
res = fns[i](res);
}
returnres; }}Copy the code
Application framework
Learning React, Vue and other frameworks is inevitably exposed to functional programming
React is a typical functional programming framework, emphasizing that a component cannot modify a prop value passed in; In Redux, immutable data is emphasized. Each reducer cannot directly modify state, but only return a new state. (State is read-only and reducer must be a pure function).
Redux create middleware (Currified)
const createMiddleware = store= > next= > action= > {
// Middleware implementation
}
// The code above looks like this:
const createMiddleware = store= > {
return next= > {
return action= > {
// Implement it}}}Copy the code
Redux-thunk Middleware
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) = > next= > action= > {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
Copy the code
Redux composite middleware
function compose(middlewares) {
return middlewares.reduce((prev, next) = > {
return (. args) = > {
returnprev(next(... args); }})}Copy the code
Koa composite middleware
function compose (middleware) {
/ * * *@param {Object} context
* @return {Promise}
* @api public* /
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if(! fn)return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
Copy the code
The Koa middleware mechanism is the idea of functional composition, which combines a set of sequentially executed functions into a single function. The outer function’s parameters are the return values of the inner function.
const mid1 = function (ctx, next) {}
const mid2 = function (ctx, next) {}
const mid3 = function (ctx, next) {}
Copy the code
When the first middleware executes, we need to get the following structure:
mid1(ctx, () => mid2(ctx, () => mid3(ctx, () => {})))
Copy the code
You can imagine simulating this with recursion:
const middlewares = [mid1, mid2, mid3];
function dispatch(i) {
return middlewares[i]()(ctx, () => dipatch(i+1));
}
dispatch(0);
Copy the code
The advantages of functional programming
- Drive the decomposition of tasks into simple functions
- Use a streaming call chain to process the data
- Reduce the complexity of event-driven code through reactive paradigms.
Disadvantages of functional programming
- Recursive trap. In functional programming languages, almost all loops are recursively replaced by immutable data structures. However, recursion requires a large number of call records, which can easily lead to stack overflow if not used properly.
- Performance overhead. The use of currification, partial functions, closures, and so on can be somewhat overhead, and function nesting takes up more memory than normal functions. It requires wrapping a method, which incurs the performance overhead of context switching.
- Resource usage. To make objects immutable, you often need to create new objects, which is more stressful for garbage collection.
conclusion
- Functional programming is a programming paradigm that emphasizes the use of functions
- In functional programming, combining multiple different functions is a very important idea
- Functional programming uses functions as building blocks to make code more modular and reusable through higher-order functions
- In some cases, functional programming is not optimal.
Refer to && recommend
Best practices for functional programming
JS functional programming guide
Analysis of functional programming
functional light JS