I need help understanding the rest and spread operator

后端 未结 3 401
無奈伤痛
無奈伤痛 2021-01-15 09:13

This is the code:

const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));

So by (...fns) the fns arguments beco

相关标签:
3条回答
  • 2021-01-15 09:43

    For better understanding I have translated the code to block level as below trying to explain the code in question for a novice developer which helps. For better implementation @naomik has solution.

    ES6

    const Pipe = (...fns) => {
        return fns.reduce((f, g) => {
            return (...args) => {
                return g(f(...args))
            }
        })
    };
    

    Equivalent ES5 implementation:

    var Pipe = function () {
        var fns = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            fns[_i] = arguments[_i];
        }
        return fns.reduce(function (f, g) {
            return function () {
                var args = [];
                for (var _i = 0; _i < arguments.length; _i++) {
                    args[_i] = arguments[_i];
                }
                return g(f.apply(void 0/*Undefined or can use 'null'*/, args));
            };
        });
    };
    

    Part by part breakdown:(read the code comments for better understanding)

    const inc = (num) => num+1 //Function to increment the number
    const dbl = (num) => num*2 //Function double's the number
    const sqr = (num) => num*num //Function Square's the number
    
    /*Function breakdown*/
    const _pipe = (f, g) => (...args) => g(f(...args)); //Second part
    const Pipe = (...fns) => fns.reduce(_pipe); //First part
    
    const incDblSqr = Pipe(inc, dbl, sqr) //Piping all the functions
    const result = incDblSqr(2) // Piped function
    console.log(result) // ((2+1)*2) ^ 2  = 36
    

    Explaination:

    The code in question helps to pipe the result from one function to another function.

    Step by step process of above code:

    • First number is incremented by 1
    • The result after increment is accumulated and piped to another function where the number gets multiplied by 2 (doubling it)
    • Finally the doubled number is squared next function.

    All these are achieved using closures which has access to the reducing arguments (Read the article linked below for clarity).

    Conclusion: The code in question helps to pipe the n-number of functions that operates on the result emitted by previous function.


    Best article by Andrew L. Van Slaars: https://vanslaars.io/post/create-pipe-function/

    Please read the above article for clarity and also @naomik solution

    0 讨论(0)
  • 2021-01-15 10:03

    Well @naomik beat me to just about the same answer but I thought I'd still share what I had to help you explain how the function works in a possibly less cryptic way. (Maybe)

    I think you already know how the "..." works (and in case you didn't then naomik's answer should help with that :D)

    Here is another version of that same pipe function expect re-written to better explain what's going on using assignments just to explain the point.

    Array.prototype.reduce calls the "reducer", toASingleFunction, multiple times--one call for each function in functionsToPipe. The currentFunctionToPipe is first x => x + 1 then x => x * 2 and so on...

    The first value of newFunction is theIdentityFunction and the reducer returns another function nextNewFunction. As the name suggests, it becomes the next newFunction in the next call to the "reducer" (toASingleFunction).

    Once all the items in functionsToPipe have been reduced, the final newFunction is returned as finalPipeFunction.

    /**
     * `x` goes to itself
     */
    const theIdentityFunction = x => x;
    
    /**
     * the `reducer` that reduces the array of functions into a single function
     * using the value from the last function as the input to the next function
     */
    const toASingleFunction = (newFunction, currentFunctionToPipe) => {
      const nextNewFunction = function(value) { // i'm not using arrow functions here just to explicitly show that `nextNewFunction` is, in fact, a function
        const valueFromLastFunction = newFunction(value);
        return currentFunctionToPipe(valueFromLastFunction);
      }
      return nextNewFunction;
    };
    
    const pipe = (...functionsToPipe) => {
      const finalPipeFunction = functionsToPipe.reduce(toASingleFunction, /* start with */ theIdentityFunction);
    
      return finalPipeFunction;
    }
    
    const f = pipe(
      x => x + 1,
      x => x * 2,
      x => x * x
    );
    
    console.log(f(2)) // ((2 + 1) * 2) ^ 2 === 36

    Maybe this helps?

    good luck!

    0 讨论(0)
  • 2021-01-15 10:04

    Your first problem is you're dealing with a bad implementation of pipe – the second problem is there's a variety of spread syntaxes in new JavaScript and it's not always clear (to beginners) which one is being used where

    rest parameter

    a rest parameter collects the supplied arguments to a function in an array. This replaces the old arguments object of JavaScript's yesteryear

    const f = (...xs) =>
      xs
      
    console.log(f())    // []
    console.log(f(1))   // [1]
    console.log(f(1,2)) // [1,2]


    spread arguments

    spread arguments allows you to spread an array (or any iterable) as arguments to a function call. This replaces (almost all) instances of Function.prototype.apply

    const g = (a,b,c) =>
      a + b + c
      
    const args = [1,2,3]
    
    console.log(g(...args)) // 6


    why that pipe is bad

    It's not a total function – pipes domain is [Function] (array of functions), but this implementation will produce an error if an empty array of functions is used (TypeError: Reduce of empty array with no initial value)

    It might not be immediately apparent how this would happen, but it could come up in a variety of ways. Most notably, when the list of functions to apply is an array that was created elsewhere in your program and ends up being empty, Pipe fails catastrophically

    const foo = Pipe()
    foo(1)
    // TypeError: Reduce of empty array with no initial value
    
    const funcs = []
    Pipe(...funcs) (1)
    // TypeError: Reduce of empty array with no initial value
    
    Pipe.apply(null, funcs) (1)
    // TypeError: Reduce of empty array with no initial value
    
    Pipe.call(null) (1)
    // TypeError: Reduce of empty array with no initial value
    

    reimplementing pipe

    This is one of countless implementations, but it should be a lot easier to understand. We have one use of a rest parameter, and one use of a spread argument. Most importantly, pipe always returns a function

    const pipe = (f,...fs) => x =>
      f === undefined ? x : pipe(...fs) (f(x))
      
    const foo = pipe(
      x => x + 1,
      x => x * 2,
      x => x * x,
      console.log
    )
    
    foo(0) // 4
    foo(1) // 16
    foo(2) // 36
    
    // empty pipe is ok
    const bar = pipe()
    console.log(bar(2)) // 2


    "but i heard recursion is bad"

    OK, so if you're going to pipe thousands of functions, you might run into a stack overflow. In such a case, you can use the stack-safe Array.prototype.reduce (or reduceRight) like in your original post.

    This time instead of doing everything within pipe, I'm going to decompose the problem into smaller parts. Each part has a distinct purpose, and pipe is now only concerned with how the parts fit together.

    const comp = (f,g) => x =>
      f(g(x))
    
    const identity = x =>
      x
      
    const pipe = (...fs) =>
      fs.reduceRight(comp, identity)
    
    const foo = pipe(
      x => x + 1,
      x => x * 2,
      x => x * x,
      console.log
    )
    
    foo(0) // 4
    foo(1) // 16
    foo(2) // 36
    
    // empty pipe is ok
    const bar = pipe()
    console.log(bar(2)) // 2


    "I really just want to understand the code in my post though"

    OK, let's step thru your pipe function and see what's happening. Because reduce will call the reducing function multiple times, I'm going to use a unique renaming for args each time

    // given
    const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));
    
    // evaluate
    Pipe(a,b,c,d)
    
    // environment:
    fns = [a,b,c,d]
    
    // reduce iteration 1 (renamed `args` to `x`)
    (...x) => b(a(...x))
    
    // reduce iteration 2 (renamed `args` to `y`)
    (...y) => c((...x) => b(a(...x))(...y))
    
    // reduce iteration 3 (renamed `args` to `z`)
    (...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z))

    So what happens then when that function is applied? Let's have a look when we apply the result of Pipe(a,b,c,d) with some argument Q

    // return value of Pipe(a,b,c,d) applied to `Q`
    (...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z)) (Q)
    
    // substitute ...z for [Q]
    d((...y) => c((...x) => b(a(...x))(...y))(...[Q]))
    
    // spread [Q]
    d((...y) => c((...x) => b(a(...x))(...y))(Q))
    
    // substitute ...y for [Q]
    d(c((...x) => b(a(...x))(...[Q]))
    
    // spread [Q]
    d(c((...x) => b(a(...x))(Q))
    
    // substitute ...x for [Q]
    d(c(b(a(...[Q])))
    
    // spread [Q]
    d(c(b(a(Q)))

    So, just as we expected

    // run
    Pipe(a,b,c,d)(Q)
    
    // evalutes to
    d(c(b(a(Q))))
    

    additional reading

    I've done a lot of writing on the topic of function composition. I encourage you to explore some of these related questions/I've done a lot of writing on the topic of function composition. I encourage you to explore some of these related questions/answers

    If anything, you'll probably see a different implementation of compose (or pipe, flow, et al) in each answer. Maybe one of them will speak to your higher conscience!

    • How to understand curry and function composition using Lodash flow?
    • lodash curry does not work on function returned by flow; lodash FP enough for FP?
    • functional composition of a boolean 'not' function (not a boolean value)
    • How to compose functions of varying arity using Lodash flow?
    • How to reconcile Javascript with currying and function composition
    0 讨论(0)
提交回复
热议问题