preface

I recently started working on Redux, which is based on the compose composition function. The way the compose function is implemented in REdux also takes full advantage of Reduce. The concept of a function is also somewhat obscure to me.

reduce MDN

Reduce is an array manipulation method from ES5. This method is described in MDN as (reduce method accumulates each value of the array from left to right through a given execution function, and finally produces a result).

This section describes reduce parameters

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
Copy the code

Callback is a callback function

  • Accumulator Is the last accumulated value.
  • CurrentValue array traverses the function being processed
  • Index The corresponding index (0 if initialValue was provided, 1 otherwise)
  • Array is the array of method calls

InitialValue is the initialValue of the first argument to the callback. No initial value is provided for the first element of the array as the first parameter of the callback

Implementation Principle of Reduce

The related Polyfill code is also available on MDN. To make the code work, I removed the comments and changed them slightly.

let obj = [10.11.13];

Object.defineProperty(obj, 'reduce', {
    value: function (callback /*, initialValue*/) {
        if (this= = =null) {
            throw new TypeError('Array.prototype.reduce ' + 'called on null or undefined');
        }
        if (typeofcallback ! = ='function') {
            throw new TypeError(callback + ' is not a function');
        }
        // 1. Let O be ? ToObject(this value).
        var o = Object(this);
        // 2. Let len be ? ToLength(? Get(O, "length")).
        var len = o.length >>> 0;
        // Steps 3, 4, 5, 6, 7
        var k = 0;
        var value;
        if (arguments.length >= 2) {
            value = arguments[1];
        } else {
            while(k < len && ! (kin o)) {
                k++;
            }
            if (k >= len) {
                throw new TypeError('Reduce of empty array ' +
                    'with no initial value');
            }
            value = o[k++];
        }
        // 8. Repeat, while k < len
        while (k < len) {
            if (k in o) {
                value = callback(value, o[k], k, o);
            }
            // d. Increase k by 1.
            k++;
        }
        // 9. Return accumulator.
        returnvalue; }});console.log(obj.reduce((cur, currItem) = > {
    return cur + currItem
}))
Copy the code

Above, I defined an array [1,2,3] to execute the custom reduce function. The execution of each step is as follows.

– First case: the first value of the array will be used as the initial value if no initial value is passed.

Number of executions k value The value passed to the callback function
The initial state 0 undefined
For the first time, 1 10 The callback (10, 11, 1, final three [10])
The second time 2 21 The callback (21, 12, 2, final three [10])
The third time 3 34 The callback (34, undefined, 3, final three [10])
  • The second case passes an initial value

When you pass the initial value, you just pass the second parameter of the function as the initial value in the initial state step

if (arguments.length >= 2) {
   value = arguments[1];
}
Copy the code

Reduce Advanced Applications

(1) Execute the promise function sequentially

const f1 = () = > new Promise((resolve, reject) = > {
    setTimeout(() = > {
        console.log('p1 running')
        resolve(1)},1000)})const f2 = () = > new Promise((resolve, reject) = > {
    setTimeout(() = > {
        console.log('p2 running')
        resolve(2)},1000)})const array = [f1, f2];
const runPromiseAarrayFun = (array, value) = > {
    return array.reduce((promisefn, currentFunction) = > promisefn.then(currentFunction), Promise.resolve(value))
}
runPromiseAarrayFun(array, 'init')
Copy the code
  • The output
p1 running
p2 running
Copy the code
  • Note: In fact, es6 already has asynchronous function “synchronous execution scheme”. For example async await can also implement promise synchronous execution. But async await can only execute a fixed number of asynchronous functions. Using async await cannot be done when the number of asynchronous functions is uncertain.

(2) Pipe implementation principle

Pipe is a Corrified function that takes a parameter as an initial value, and works by solving the problem that g(f(m(… Arg))) nesting depth uncertainty when nesting calls.

  • Example: from MDN
// Building-blocks to use for composition
const double = x= > x + x;
const triple = x= > 3 * x;
const quadruple = x= > 4 * x;
// Function composition enabling pipe functionality
const pipe = (. functions) = > input= > functions.reduce(
    (acc, fn) = > {
        debugger
        return fn(acc)
    },
    input
);
const multiply24 = pipe(double, triple, quadruple);
multiply24(10); / / 240
Copy the code

When multiply24(10) is executed, the above three functions are executed in sequence for parameter 10, but the value of the next function is the result of the last one. This feature is similar to the principle of Reduce.

  • Implementation process
Number of executions value The value passed to the next function equivalent
The initial state 10
For the first time, 10 double(10) double(10)
The second time 20 triple(20) triple(double(10))
The third time 60 quadruple(60) quadruple(triple(double(10)))

compose

Compose is also known as a composition function. His principle is similar to pipe’s. Mainly used to perform a series of tasks of indefinite length.

  • Pipe execution order
letfunarrs = [f1,f2,f3,f4]; pipe(... funarrs); Equivalent to the f4 (f3 (f2 (f1 (... args))));Copy the code
  • Pipe execution order
letfunarrs = [f1,f2,f3,f4]; pipe(... funarrs); Equivalent of f1, f2, f3, f4 (... args))));Copy the code

Recursive implementation

const compose = function (. args) {
    let length = args.length;
    let count = length - 1;
    let result
    return function f1(. arg1) {
        debugger
        result = args[count].apply(this, arg1);
        if (count <= 0) {
            count = length - 1;
            return result;
        }
        count--;
        console.log('countttttttttttttttt')
        return f1.call(null, result)
    }
}
function f1() {
    console.log("f1")
    return 1;
}
function f2() {
    console.log("f2")
    return 2;
}
function f3() {
    console.log("f3");
    return 3;
}
function f4() {
    console.log("f4")
    return 4;
}
let funcs = [f1, f2, f3, f4];
letcomposeFunc = compose(... funcs)Copy the code
Number of executions Does the recursion end The count value Executive function arg
For the first time, no 3 f4 empty
The second time no 2 f3 4
The third time no 1 f2 3
For the fourth time is 0 f1 f1.apply(this, arg1) 3

The closure is used here, and the count argument in the compose function is cached. The logic of each execution is to take the last return value as the parameter of this execution, and the procedure can use recursion, and the recursion ends when count==0, and the recursion breaks out. Execute the f1 function directly.

Reduce implementation

const compose = (. args) = > {
    return args.reverse().reduce((f, g) = > {
        return (. arg) = > {
            return g.call(this, f.apply(this, arg))
        }
    }, args.shift())
}
function f1(x) {
    return x + 1;
}
function f2(x) {
    return x + 2;
}
function f3(x) {
    return x + 3;
}
function f4(x) {
    return x + 4;
}
let funcs = [f1, f2, f3, f4]
console.log(compose(... funcs)(0))
Copy the code
  • The initial value of 0 for the first time — > the args. The shift () = = f4 – > right all (this, f.a pply (this, arg)) < = > f3. Call (this, f4. Apply (this, 0));
  • F2. call(this, f3.call(this, f3.call(this, f3.call(this, f4.apply(this, 0))));
  • Third time 0 – > right all (this, f.a pply (this, arg)) < = > f1. The call (this, f2. Apply (this, f2. Call (this, f3. Apply (this, f4. Apply (this, 0)))));
  • Apply (this, f2.call(this, f2.call(this, f3.apply(this, f3.apply(this, f3.apply(this, f4.apply(this, 0)))));

The principle of reduce to realize compose is to use reduce to accumulate new information and take the last accumulated function value as the parameter of the current function.

The resources

  1. pipe
  2. compose