Let’s cut to the chase.

Why learn functional programming?

Functional programming is a very old concept:

  • Functional programming has gained more attention with the popularity of React. (High-order components of React are implemented using high-order functions, which are a feature of functional programming. Redux also uses the idea of functional programming.
  • Vue3 is also embracing functional programming
  • Functional programming can get rid of this to offset the problem of this pointing
  • Better use of Tree Shaking to filter out useless code during packaging
  • Convenient for testing and parallel processing
  • There are many libraries that help us with functional programming development: Lodash, underscore, Ramda, and more

What is functional programming?

Functional programming, abbreviated FP, is a programming paradigm and a programming style that goes hand in hand with object orientation. Functional programming can be thought of as a mode of thinking, plus an implementation. The way they think about it is abstracting things from the real world and the connections between things into the procedural world.

Common paradigms include procedural programming (implemented in steps) and object-oriented programming (abstracted from the real world into classes and objects, demonstrating the connections between different things through encapsulation, inheritance, and polymorphism).

The difference between functional programming and object-oriented programming

  • From the way of thinking

Object-oriented programming is the abstraction of things, encapsulating behavior into data entities in the way of object methods, so as to reduce the coupling degree of the system. While functional programming is the abstraction of the operation process, the data in the way of input and output flow encapsulation into the process, so as to reduce the coupling degree of the system.

Understanding of functional programming thinking:

  • The essence of the program: according to the input through a certain operation to obtain the corresponding output, the program development process will involve many input and output functions.
  • A Function in functional programming is not a Function in a program, but a mapping in mathematics, such as y=f(x), which is the relation between x and y
  • The same input always produces the same output (pure function)
  • Functional programming is used to describe mappings between data (functions)
// Non-functional
let num1 = 2
let num2 = 3
let sum = num1 + num2
console.log(sum)

/ / function type
function add(a, b) {
    return a + b
}
let sum = add(2.3)
console.log(sum)
Copy the code

A primer on functional programming

The function is a first-class citizen

In JS, a Function is just an ordinary object. We can store the Function in a variable/array, it can be used as an argument and return value of another Function, or we can even construct a new Function at runtime by using new Function(‘alert(1)’).

  • Functions can be stored in variables
// Assign functions to variables (function expressions)
let fn = function () {
    console.log("hi")
}

fn() 
Copy the code

The following two features are explained in more detail in higher-order functions

  • Function as argument
  • Function as the return value

Higher-order functions

What is a higher-order function?

Higher-order functions

  • Function as argument
/ / simulation forEach
function forEach(arr, fn) {
    for(let i = 0; i < arr.length; i++) { fn(arr[i]); }}const arr1 = [1.2.3.4];
forEach(arr1, item= > {
    item = item * item;
    console.log(item);
})
Copy the code
  • Function as the return value
function createFn() {
    let name = 'ming';
    return function() {
      	console.log(`my name is ${name}`); }}let getName = createFn();
// Call method 1
getName(); // my name is ming
// Call method 2
createFn()(); // my name is ming
Copy the code
// Simulate the once function, executed only once
function once(fn) {
    let flag = false;
    return function() {
        if(! flag) { flag =true;
            fn.apply(this.arguments); }}}let commitOrder = once((time) = > {
    console.log('The order was submitted${time}Time `);
})
commitOrder(1); // The order was submitted once
commitOrder(2);
commitOrder(3);
Copy the code

The meaning of using higher-order functions

  • Higher-order functions can be used to abstract generic problems (just knowing our goals and defining functions to solve such problems, without worrying about implementation details)

Commonly used higher-order functions

There is a general feature that requires a function as an argument.

  • forEach
function forEach(arr, fn) {
    for(let i = 0; i < arr.length; i++) { fn(arr[i]); }}const arr1 = [1.2.3.4];
forEach(arr1, item= > {
    item = item * item;
    console.log(item);
})
Copy the code
  • map

Each element in the array is iterated over and processed, the result of which is returned in a new array

function map(arr, fn) {
    const result = [];
    for(let i = 0; i < arr.length; i++) {
      	result.push(fn(arr[i]));
    }
    return result;
}

const arr2 = [1.2.3.4];
const newArr2 = map(arr2, item= > item * item)
console.log(newArr2);
Copy the code
  • filter

Creates a new array of elements by checking all the elements in the specified array.

function filter(arr, fn) {
    const result = [];
    for(let i = 0; i < arr.length; i++) {
        if(fn(arr[i])) { result.push(arr[i]); }}return result;
}

const arr3 = [1.3.4.5.8.9];
const newArr3 = filter(arr3, item= > item % 2= = =0);
console.log(newArr3);
Copy the code
  • every

Whether each element in the array matches one of the conditions we specified, return true if all, false if none

function every(arr, fn) {
    let flag = true;
    for (let item of arr) {
        flag = fn(item);
        if(! flag) {break; }}return flag;
}

const arr5 = [2.4.6.8.9];
const q = every(arr5, item= > item % 2= = =0);
console.log(q);
Copy the code
  • some

Check if there is an element in the array that meets any of the conditions we specify, true, and false

function some(arr, fn) {
    let flag = false;
    for(let item of arr) {
        flag = fn(item);
        if(flag) {
          	break; }}return flag;
}

const arr4 = [2.3.6.8.9];
const result = some(arr4, item= > item > 7);
console.log(result);

Copy the code
  • find/findIndex
  • reduce
  • sort

closure

The concept of closures

Closures: A function is bundled with references to its surrounding state (lexical context) to form closures (closures allow you to access the scope of an outer function within an inner function)

  • You can call an inner function of a function from another scope and access members of that function scope

In the above function as the return value, we actually use the closure, here is the syntax:

function createFn () {
    let name = 'ming';
}
// When createFn is executed, the name variable will be released
// But the following situation

function createFn () {
    let name = 'ming';
    return function() {
      	console.log(`my name is ${name}`); }}A closure is a function that returns a function and calls a member inside the function

let getName = createFn();
getName(); // my name is ming
// getName is an external function. When an external function has a reference to an internal member, the internal member name cannot be released. When we call getName, we access the name.

Copy the code

The core role of closures

Enlarges the scope of a function’s internal members

The nature of closures

Functions are placed on an execution stack during execution and removed from the stack when they are finished. However, scoped members on the heap cannot be freed because they are referenced externally, so the inner function can still access the members of the outer function.

The function is on the execution stack at the time of execution, and is removed from the execution stack after execution, freeing the memory of the internal member. However, after the function is removed, the memory of the internal member cannot be freed if there is an external reference. /

The case of closures

Case a

The operation to calculate the square and cube of a number

Math.pow(4.2)
Math.pow(5.2)
// The second and third power is repeated many times, and now we have to write a second and third function
function makePower (power) {
  return function (number) {
    return Math.pow(number, power)
  }
}

/ / square
let power2 = makePower(2)
let power3 = makePower(3)

console.log(power2(4)) / / 16
console.log(power2(5)) / / 25
console.log(power3(4)) / / 64
Copy the code
Case 2

Calculate the salary of different levels of employees

// Assume that the first function is passed the basic salary and the second parameter is passed the performance salary
// getSalary(12000, 2000)
// getSalary(15000, 3000)
// getSalary(15000, 4000)

// The base salary is the same at different levels, so we take the base salary out and then just add merit pay
function cretateSalary (base) { 
    return function (performance) { 
        return base + performance 
    }
}
let salaryLevel1 = cretateSalary(12000)
let salaryLevel2 = cretateSalary(15000)

console.log(salaryLevel1(2000)) / / 14000
console.log(salaryLevel2(3000)) / / 18000
console.log(salaryLevel2(4000)) / / 19000
Copy the code

Pure functions

The concept of pure functions

The same input will always get the same output, without any observable side effects.

A pure function is like a function in mathematics, y = f(x).

let numbers = [1.2.3.4.5];
/ / pure functions
For the same input, the output is always the same

// The slice method returns the truncated function without affecting the original array
numbers.slice(0.3); / / [1, 2, 3]
numbers.slice(0.3); / / [1, 2, 3]
numbers.slice(0.3); / / [1, 2, 3]

// impure function
// For the same input, the output is different

// the splice method returns the array and changes the array
numbers.splice(0.3); / / [1, 2, 3]
numbers.splice(0.3); / / [4, 5]
numbers.splice(0.3); / / []

// The following functions are also pure functions
function getSum (n1, n2) {
    return n1 + n2;
}
console.log(getSum(1.2)); / / 3
console.log(getSum(1.2)); / / 3
console.log(getSum(1.2)); / / 3
Copy the code
  • Functional programming does not preserve intermediate results, so variables are immutable (stateless)
  • We can also hand over the results of one function to another

Lodash — Stands for pure functions

  • Lodash is a pure function library that provides modularity, high performance, and additional functionality. Provides methods for operating on arrays, numbers, objects, strings, functions, and more

Try the shallow Lodash

  • The installation

npm init -y -> npm i lodash

  • experience
const _ = require('lodash');

const array = ['a'.'b'.'c'.'d'];

// The alias of head is first. Head (array) can also be used
console.log(_.first(array)); // a
console.log(_.last(array)); // d

console.log(_.toUpper(_.first(array))); // A

console.log(_.reverse(array));  // [ 'd', 'c', 'b', 'a' ]
// Array flipping is not a pure function because it changes the original array. Here, the reserve uses array reverse, so it is not a pure function

const r = _.each(array, (item, index) = > {
  console.log(item, index)
  // d 0
  // c 1
  // b 2
  // a 3
})
console.log(r) // [ 'd', 'c', 'b', 'a' ]
Copy the code

The benefits of pure functions

cacheable

Because there is always the same result for the same input, you can cache the results of pure functions to improve performance.

const _ = require('lodash')

function getArea(r) {
  console.log(r)
  return Math.PI * r * r
}

let getAreaWithMemory = _.memoize(getArea)
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
/ / 4
/ / 50.26548245743669
/ / 50.26548245743669
/ / 50.26548245743669
Copy the code

Next, simulate a memory function memoize

function memoize(fn) {
    const cache = {};
    return function () {
        const key = JSON.stringify(arguments);
        return cache[key] || (cache[key] = fn.apply(fn, arguments)); }}function getSum(a, b) {
    console.log('getSum is called ');
    return a + b;
}

let getSumWithMemory = memoize(getSum);
console.log(getSumWithMemory(2.3)); // getSum is called 5
console.log(getSumWithMemory(2.3)); / / 5
console.log(getSumWithMemory(2.3)); / / 5

// getSum is called
/ / 5
/ / 5
/ / 5
Copy the code

testable

Pure functions make testing easier

Parallel processing

  • Parallel manipulation of shared memory data in a multithreaded environment is likely to cause unexpected problems. Pure functions do not need to access shared memory data, so they can be run arbitrarily in parallel
  • Although JS is a single thread, a new Web Worker emerging after ES6 can start a new thread

Side effects

A side effect is to make a function impure. A pure function is based on the fact that the same input always returns the same output. If the function depends on the external state, it cannot guarantee the same output.

// Impure function because it depends on external variables
let min = 18;
function checkAge (age) { 
    return age >= min;
}
Copy the code

Sources of side effects:

  • The configuration file
  • The database
  • Get user input
  • .

All external interaction may cause side effects, side effects which resulted in the reduction method is universal is not suitable for extended and reused, and side effects will bring potential safety hazard to the application, bring uncertainty to the program, but the side effects could not be completely banned, we can’t forbid user input user name and password, can only try to control them under control.

Currie,

Solve the hard coding problem

// The following code solves the problem of impure functions, but it is hard coded
function checkAge (age) { 
    let min = 18;
    return age >= min;
}

// a plain pure function
function checkAge (min, age) {
    return age >= min;
}
console.log(checkAge(18.20));  // true
console.log(checkAge(18.24));  // true
console.log(checkAge(20.24));  // true

// It is often compared with 18, which is repeated. This can be overwritten using closures
// ES5
function checkAge (min) {
    return function (age) {
        returnage >= min; }}// ES6
let checkAge = min= > (age= > age >= min);

let checkAge18 = checkAge(18);
let checkAge20 = checkAge(20);

console.log(checkAge18(20)); // true
console.log(checkAge18(24)); // true
Copy the code

Corrification: We can modify a function when it has more than one parameter. We can call a function, pass only part of the argument (which will never change), and have the function return a new function. The new function passes the remaining arguments and returns the corresponding result.

Currization in Lodash — Curry ()

_.curry(fn)

  • Function: creates a function that takes one or more parameters from the fn, executes the fn if all the parameters required by the FN are provided, and returns the result of the execution. Otherwise continue to return the function and wait for the remaining arguments to be received.
  • Parameters: functions that require currization
  • Return value: The currified function
const _ = require('lodash');

// A function of several elements corresponds to several parameters. The parameters are one unary function and two binary functions
// It is possible to transform a function of several variables into a function of one variable
function getSum (a, b, c) {
  return a + b + c;
}

// Define a Currization function
const curried = _.curry(getSum);

// If all parameters are entered, the result is returned immediately
console.log(curried(1.2.3)); / / 6

// If a partial argument is passed in, it returns the current function and waits to receive the remaining arguments in getSum
console.log(curried(1) (2) (3)); / / 6
console.log(curried(1) (2.3)); / / 6
console.log(curried(1.2) (3)); / / 6
Copy the code

case

To determine if whitespace exists in a string, or to extract all whitespace from a string, use the string match method:

' '.match(/\s+/g)
Copy the code

But if we write a method to remove whitespace from an array, the code above cannot be reused. So how do we write it functionally

function match(reg, str) {
  return str.match(reg)
}
Copy the code

The expression of reg is repeated. How to corrify the function above is as follows:

// Currization
const _ = require('lodash')

// Using Lodash's curry function, the first argument is a matching rule, and the second argument is a string, to generate a match function
const match = _.curry(function (reg, str) {
  return str.match(reg)
})

// haveSpace is a function that matches Spaces according to the rule
const haveSpace = match(/\s+/g)

console.log(haveSpace("hello world")) // [' ']
console.log(haveSpace("helloworld")) // null
// Check if there are Spaces in the string

// What about numbers?
// By rule haveNumber is a function that matches numbers
const haveNumber = match(/\d+/g)
console.log(haveNumber('abc')) // null

// How do arrays match elements with Spaces
const filter = _.curry(function(func, array) {
  return array.filter(func)
})

// The filter function, the first argument passes whether there is a space in the matched element
// The second argument is the specified array
console.log(filter(haveSpace, ['John Connor'.'John_Donne'])) // [ 'John Connor' ]

// If the above is still too much trouble, then you can wrap another function
// Filter can pass a parameter and return a function
// findSpace is a function that matches if there is a space in an array element
const findSpace = filter(haveSpace)
console.log(findSpace(['John Connor'.'John_Donne'])) // [ 'John Connor' ]
Copy the code

As a small summary of the above ideas, the advantage of Currization is that it maximizes the reuse of functions.

const _ = require('lodash')

// The match function returns the result of a match based on the re string
const match = _.curry(function (reg, str) {
  return str.match(reg)
})

// haveSpace is a function that matches Spaces
const haveSpace = match(/\s+/g)

// The haveNumber function is a function that matches numbers
const haveNumber = match(/\d+/g)

The filter function defines an array and a filter rule, and returns an array that matches the matching rule
const filter = _.curry(function(func, array) {
  return array.filter(func)
})

The findSpace function is a function that matches an array element with a space and returns an array that matches that
const findSpace = filter(haveSpace)
Copy the code

Simulation of the Currization principle

Let’s look at an example that we did before

const _ = require('lodash')

function getSum (a, b, c) {
  return a + b + c
}

const curried = _.curry(getSum)

console.log(curried(1.2.3))  / / 6
console.log(curried(1) (2.3))  / / 6
console.log(curried(1.2) (3))  / / 6
Copy the code

Implementing a Coriolization transformation function requires analysis

  1. Call-in and call-out: The calling function passes in a pure function argument and returns a Corrified function when done
  2. Analysis of entry conditions:
  • If the curried call passes the same number of arguments as the getSum function, it executes immediately and returns the result of the call
  • If the arguments passed by the Curried call are partial arguments to getSum, you need to return a new function and wait to receive the other arguments to getSum
  1. Focus on:
  • Gets the arguments to the call
  • Check if the numbers are the same
// Simulate the Currization function
function curry (fn) {
  return function curriedFn(. args) {
    if(args.length < fn.length) {
      return function() {
        returncurriedFn(... args.concat(Array.from(arguments)))}}returnfn(... args) } }const curriedTest = curry(getSum)

console.log(curriedTest(1.2.3))  / / 6
console.log(curriedTest(1) (2.3))  / / 6
console.log(curriedTest(1.2) (3))  / / 6
console.log(curriedTest(1) (2) (3))  / / 6
Copy the code

Corrified summary

  • Corrification allows us to pass fewer arguments to a function and get a new function that remembers some of the fixed arguments (for example, the match function is regenerated as a haveSpace function that uses closures and remembers the arguments to the regular expression that we passed).
  • This is a ‘cache’ of function arguments (using closures)
  • Make the function more flexible, make the function smaller granularity
  • You can convert multivariate functions into unary functions, and you can combine functions to produce powerful functions

reference

  • Functional programming is north
  • Introduction to functional programming