Part1 · JavaScript [In-depth Analysis]

Functional programming and Js asynchronous programming, handwritten Promise

Article description: The content of this column is my study notes and thinking summary of [Hook big front end high-tech training camp], the heart of an apprentice, only for sharing. If not, please pay in the comments section. If you think the column is good, please like, follow, or comment.

Progress together!

Last post: Higher-order Functions, Closures

Next: Functors

The previous article mentioned in js part of the pre-knowledge: [higher-order function], [closure], click the above link to view.


### List of articles *Part1 · JavaScript [In-depth Analysis]

  • Functional programming and Js asynchronous programming, handwritten Promise
    • @[toc]
  • 4. Pure functions
    • 1. Pure functional concepts
    • 2. Benefits of pure functions
    • 3. The side effects
  • 5. Haskell Brooks Curry
    • 1. Curryized functions in Lodash
    • 2. To summarize
  • Function combination
    • 1. The pipe
    • 2. Function combination
    • 3. The debugging
    • 4.Point Free

4. Pure functions

1. Pure functional concepts

  • Pure functions: The same input always yields the same output, without any observable side effects

    • A pure function is like a function in mathematics (which describes the relationship between input and output), y=f(x).
  • Lodash is a pure function library that provides methods for manipulating arrays, numbers, objects, strings, functions, and more

  • The arrays slice and splice are pure and impure functions, respectively

    • Slice returns the specified portion of the array without changing the original array
    • Splice operates on an array and returns that array, and it changes the array
let numbers = [1.2.3.4.5];
/ / pure functions
numbers.slice(0.3)
/ / = > [1, 2, 3]
numbers.slice(0.3)
/ / = > [1, 2, 3]
numbers.slice(0.3)
/ / = > [1, 2, 3]

// Impure function
numbers.splice(0.3)
/ / = > [1, 2, 3]
numbers.splice(0.3)
/ / = > [4, 5)
numbers.splice(0.3)
/ / = > []
Copy the code
  • Functional programming does not preserve the intermediate results of calculation, so variables are immutable (stateless)
  • We can hand over the results of one function to another

2. Benefits of pure functions

  • cacheable

    • Because pure functions always have the same result for the same input, the result of a pure function can be cached
    const _ = require('lodash')
    
    function getArea(r) {
        return Math.PI * r * r
    }
    
    let getAreaWithMemory = _.memoize(getArea)
    console.log(getAreaWithMemory(4))
    Copy the code
  • Simulate a memoize function yourself

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

function memoize(f) {
    // Cache the result with an object whose key is the argument passed to f and whose value is the return value passed to f
    let cache = {}
    // Return a function in which to determine if the return value needs to be executed again
    return function () {
        // Arg_str is the argument passed to function f, which is converted to a string using json.stringify
        let arg_str = JSON.stringify(arguments)
        // Assign to cache[arg_str] in two cases:
        // 1. If the key arg_str exists in the cache, assign a value to it
        // 2. There is no arg_str key in the cache, so you need to call the passed f function
        // Call f with f.ply. The first argument refers to f itself, and the second argument is the argument passed to f, followed by its return value
        cache[arg_str] = cache[arg_str] || f.apply(f, arguments)
        return cache[arg_str]
    }
}

/ / 4
/ / 50.26548245743669
/ / 50.26548245743669
/ / 50.26548245743669
Copy the code
  • testable

    • Pure functions make testing easier
  • Parallel processing

    • Working in parallel with shared memory data in a multi-threaded environment is likely to lead to unexpected situations
    • Pure functions do not need to access shared memory data, so they can run as many pure functions as they want in a parallel environment (Web Work).

3. The side effects

  • Pure functions: Always get the same output for the same input, without any observable side effects
/ / not pure
let mini = 18

function checkAge(age) {
    return age >= mini
}

// A pure function (hard code the problem, and then collize it)
function checkAge2(age) {
    let mini = 18
    return age >= mini
}
Copy the code

The side effect of making a function impure (as shown above) is that a pure function returns the same output based on the same input, which can be a side effect if the function is dependent on the external state and cannot guarantee the same output

Source of side effects:

  • The configuration file
  • The database
  • Gets the user’s input

All external interactions may bring side effects, which also reduce the universality of methods and make them unsuitable for extension and reusability. Meanwhile, side effects will bring security risks and uncertainty to programs, but side effects cannot be completely prohibited, so they should be controlled as much as possible.

5. Haskell Brooks Curry

  • Use Currying to solve the hard-coded problem in the previous case
/ / not pure
function checkAge(age) {
    let mini = 18
    return age >= mini
}

// Plain pure function
function checkAge(min,age) {
    return age >= mini
}

checkAge(18.24)
checkAge(18.20)
checkAge(20.30)

/ / curry
function checkAge(min) {
    return function (age) {
        return age >= min
    }
}

/ / ES6 writing
let checkAge = min= > (age= > age >= min)

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

checkAge18(24)
checkAge18(20)
Copy the code
  • Currie,

    • When a function has more than one argument, it is called by passing some of the arguments (which never change).
    • It then returns a new function that takes the remaining arguments and returns the result

1. Curryized functions in Lodash

  • _.curry(func)

    • Function: Create a function that takes one or more func arguments, executes func and returns the result if all the arguments required by func are provided. Otherwise, continue returning the function and wait to receive the remaining arguments.
    • Parameters: Functions that require curryization
    • Return value: The curryized function
const _ = require('lodash');

// The function to be Curryized
function getSum(a, b, c) {
    return a + b + c
}

// The curryized function
let curried = _.curry(getSum)
/ / test
curried(1.2.3)
curried(1.2) (3)
curried(1) (2.3)
Copy the code
  • case
const _ = require('lodash')
const match = _.curry(function (reg, str) {
    return str.match(reg)
})

const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)

console.log(haveSpace('Hello World'))
console.log(haveNumber('$25'))

const filter = _.curry(function (func, array) {
    return array.filter(func)
})

console.log(filter(haveSpace, ['John Connor'.'John_Donne']))

const findSpace = filter(haveSpace)
console.log(findSpace(['John Connor'.'John Donne']))
Copy the code
  • Simulate the implementation of _.curry()
function curry(func) {
    return function curriedFn(. args) {
        // Determine the number of arguments and parameters. Func. length represents the length of the parameter of func
        if (args.length < func.length) {
            return function () {
                // Concatenate passed arguments with unpassed arguments. Concat is used for array concatenation
                returncurriedFn(... args.concat(Array.from(arguments)))}}returnfunc(... args) } }Copy the code

2. To summarize

  • Coreization allows us to pass fewer arguments to a function and get a new function that has some fixed arguments memorized
  • This is a kind of caching of function arguments
  • Make functions more flexible and less granular
  • Functions of several variables can be converted into functions of one variable, and functions can be combined to produce powerful functions

Function combination

  • Pure functions and Currying is easy to write onion code (h(g(f(x))))

    • Get the last element of the array and convert it to uppercase, *.toupper (.first( *.reverse(array)))

  • Function composition allows us to regroup fine-grained functions to produce a new function

1. The pipe

The following graph shows the process of using a function to process data in a program. Input a to fn and return b as a result. You can imagine that data A goes through a pipe to data B.

When fn is more complicated, we can break it up into smaller functions, with more m and n generated by the intermediate operations.

In the figure below, you can imagine that the pipe FN is divided into three pipes F1, F2 and F3. Data A goes through the pipe F3 to get the result M, m goes through the pipe F2 to get the result N, and n goes through the pipe F1 to get the final result B.

fn = compose(f1,f2,f3)
b = fn(a)
Copy the code

2. Function combination

  • Compose: For example, if a function has to be processed by more than one function for its final value, you can compose the intermediate functions into a single function

    • Functions are like conduits of data, and function combination is the process of connecting these conduits, allowing data to flow through multiple conduits to form the final result
    • Function combinations are executed from right to left by default
// The array of composite functions is flipped before removing the first element
function compose(f, g) {
    return function (x) {
        return f(g(x))
    }
}

function first(arr) {
    return arr[0]}function reverse(arr) {
    return arr.reverse()
}

// Execute from right to left
let last = compose(first, reverse)
console.log(last([1.2.3.4]))
Copy the code
  • Combinatorial functions in lodash
  • Lodash combines the flow() or flowRight() functions, both of which can combine multiple functions
  • Flow () runs from left to right
  • FlowRight () runs from right to left, using a little more
const _ = require('lodash')

const toUpper = s= > s.toUpperCase()
const reverse = arr= > arr.reverse()
const first = arr= > arr[0]

const f = _.flowRight(toUpper, first, reverse)
console.log(f['one'.'two'.'three'])
Copy the code
  • Simulate implementing lodash’s flowRight method
// Multifunction combination
function compose(. fns) {
    // Pass in an indefinite number of functions, using... FNS instead, remaining parameters
    // Return a function that takes a value as an argument
    return function (value) {
        // Return the call from... The last function in FNS is called, so the argument FNS needs to be flipped first
        // Each function needs to process the value and return the processed value
        // Reduce executes the provided function for each element in the array and aggregates it into a single result
        // Reduce is a function that takes two arguments, acc: the cumulative result, fn how to process each result and return a new result
        return fns.reverse().reduce(function (acc, fn) {
            // fn for each function in the array, acc last returned the result
            return fn(acc)
            // acc starts with the value passed in the first call
        }, value)
    }
}

// ES6
const compose = (. fns) = > value= > fns.reverse().reduce((acc, fn) = > fn(acc), value)
Copy the code
  • And this combination of functions is associativity

    • We can combine g with h, or we can combine f with g, and the result is the same, that is, the order of combination doesn’t matter
// associativity
let f = compose(f, g, h)
let associative = compose(compose(f, g), h) == compose(f, compose(g, h))
// true
Copy the code
  • So the code could look something like this
const _ = require('lodash')
// const f = _.flowRight(_.toUpper, _.first, _.reverse)
// const f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
const f = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse))
console.log(f(['one'.'two'.'three']))
// => THREE
Copy the code

3. The debugging

  • How do I debug composite functions
const f = _.flowRight(_.toUpper, _.first, _.reverse)
console.log(f(['one'.'two'.'three']))
Copy the code
const _ = require('lodash')
    const trace = _.curry((tag, v) = > {
    console.log(tag, v)
    return v
})
const split = _.curry((sep, str) = > _.split(str, sep))
const join = _.curry((sep, array) = > _.join(array, sep))
const map = _.curry((fn, array) = > _.map(array, fn))
const f = _.flowRight(join(The '-'), trace('after the map'), map(_.toLower),
trace('after the split), split(' '))
console.log(f('NEVER SAY DIE'))
Copy the code
  • lodash/fp

    • Lodash’s FP module provides a functional programme-friendly approach to use
    • Provides an immutable auto-curried-first data-last method
/ / lodash module
const _ = require('lodash')
_.map(['a'.'b'.'c'], _.toUpper)
// => ['A', 'B', 'C']
_.map(['a'.'b'.'c'])
// => ['a', 'b', 'c']
_.split('Hello World'.' ')
/ / lodash/fp module
const fp = require('lodash/fp')
fp.map(fp.toUpper, ['a'.'b'.'c'])
fp.map(fp.toUpper)(['a'.'b'.'c'])
fp.split(' '.'Hello World')
fp.split(' ') ('Hello World')
Copy the code
const fp = require('lodash/fp')
const f = fp.flowRight(fp.join(The '-'), fp.map(_.toLower), fp.split(' '))
console.log(f('NEVER SAY DIE'))
// never-say-die
Copy the code

4.Point Free

Point Free: We can define the process of data processing as data-independent synthetic operation, without using the parameter number representing the data, as long as the simple operation steps are synthesized together. Before using this mode, we need to define some auxiliary basic operation functions.

  • You do not need to specify the data being processed
  • You just have to synthesize it
  • Some auxiliary basic operations need to be defined
const f = fp.flowRight(fp.join(The '-'), fp.map(_.toLower), 1 fp.split(' '))
Copy the code
  • Case presentation
// Non-point Free mode
// Hello World => hello_world
function f (word) {
	return word.toLowerCase().replace(/\s+/g.'_');
}
// Point Free
const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/\s+/g.'_'), fp.toLower)
console.log(f('Hello World'))
Copy the code
  • Use the Point Free pattern to extract and capitalize the first letter in the word
const fp = require('lodash/fp')
const firstLetterToUpper = fp.flowRight(join('. '),
fp.map(fp.flowRight(fp.first, fp.toUpper)), split(' '))
console.log(firstLetterToUpper('world wild web'))
// => W. W. W
Copy the code

This is the end of today’s share. We will update the next part tomorrow: Functor.

Record: 2020/10/28

Next: Functors