Skip to content

Latest commit

 

History

History
176 lines (124 loc) · 6.01 KB

notes001-009.md

File metadata and controls

176 lines (124 loc) · 6.01 KB

Topic 009: Spread, Rest, Generators, Iterators, Currying

Topic 009: Spread, Rest, Generators, Iterators, Currying


1. Spread Operator

The spread operator (...) allows an iterable (like an array or string) to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or to be expanded in an object expression.

Example:

const array1 = [1, 2, 3];
const array2 = [4, 5, 6];

const combinedArray = [...array1, ...array2]; // Combines both arrays
console.log(combinedArray); // Output: [1, 2, 3, 4, 5, 6]

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

const combinedObject = { ...obj1, ...obj2 }; // Combines both objects
console.log(combinedObject); // Output: {a: 1, b: 2, c: 3, d: 4}

2. Rest Parameter

The rest parameter syntax (...) allows a function to accept an indefinite number of arguments as an array, providing a way to represent variadic functions in JavaScript.

Example:

function sum(...numbers) {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

console.log(sum(1, 2, 3)); // Output: 6
console.log(sum(1, 2, 3, 4, 5)); // Output: 15

3. Generators

Generators are a special class of functions that simplify the task of writing iterators. They can pause execution and resume at any time, making them useful for scenarios where you need to maintain state between iterations.

A generator function is defined using function* syntax and uses the yield keyword to yield values.

Example:

function* countUpTo(max) {
  for (let i = 1; i <= max; i++) {
    yield i;
  }
}

const counter = countUpTo(5);

console.log(counter.next().value); // Output: 1
console.log(counter.next().value); // Output: 2
console.log(counter.next().value); // Output: 3
console.log(counter.next().value); // Output: 4
console.log(counter.next().value); // Output: 5
console.log(counter.next().value); // Output: undefined (since it exceeds the max)

4. Iterators

An iterator is an object that defines a sequence and potentially a return value upon its termination. It implements the Iterator protocol by having a next method that returns an object with two properties: value (the next value in the sequence) and done (a boolean indicating whether the sequence has completed).

Example:

const myIterable = {
  [Symbol.iterator]: function () {
    let step = 0;
    return {
      next: function () {
        step++;
        if (step <= 3) {
          return { value: step, done: false };
        } else {
          return { value: undefined, done: true };
        }
      },
    };
  },
};

const iterator = myIterable[Symbol.iterator]();

console.log(iterator.next()); // Output: { value: 1, done: false }
console.log(iterator.next()); // Output: { value: 2, done: false }
console.log(iterator.next()); // Output: { value: 3, done: false }
console.log(iterator.next()); // Output: { value: undefined, done: true }

Summary

  • Spread Operator (...): Expands elements of an iterable (e.g., arrays, objects).
  • Rest Parameter (...): Collects multiple function arguments into an array.
  • Generators (function* and yield): Functions that can pause and resume execution.
  • Iterators ({ next: function }): Objects defining a sequence of values with a next method.

These features provide powerful ways to handle collections, function arguments, and custom iteration behaviors in JavaScript.


5. Currying :

  • Currying is a functional programming technique where a function that takes multiple arguments is transformed into a series of functions that each take a single argument. A popular problem that demonstrates currying involves creating a function that adds numbers in a curried manner.

Problem Description:

  • Create a curried function add such that it can be called with multiple arguments one at a time and returns the sum. The function should be able to handle any number of arguments.

Example Usage:

add(1)(2)(3); // returns 6
add(1, 2, 3); // returns 6
add(1)(2, 3)(4); // returns 10
add(1)(2)(3)(4)(5); // returns 15

Solution:

We can achieve this by implementing a function that accumulates the arguments and computes the sum when no more arguments are provided.

Here is an implementation in JavaScript:

function add(...args) {
  // Inner function to accumulate arguments
  function currySum(...innerArgs) {
    // Concatenate new arguments to the existing ones
    const allArgs = [...args, ...innerArgs];

    // Function to sum all accumulated arguments
    function sum() {
      return allArgs.reduce((acc, val) => acc + val, 0);
    }

    // Proxy to call sum when function is converted to string or number
    sum.toString = sum;
    sum.valueOf = sum;

    // Return a curried function to allow chaining
    return add(...allArgs);
  }

  return currySum;
}

// Example Usage
console.log(add(1)(2)(3).toString()); // 6
console.log(add(1, 2, 3).toString()); // 6
console.log(add(1)(2, 3)(4).toString()); // 10
console.log(add(1)(2)(3)(4)(5).toString()); // 15

Explanation:

  1. Base Function (add): This function takes any number of arguments using the rest parameter syntax (...args).
  2. Inner Function (currySum): This inner function is used to accumulate the arguments. It takes new arguments (...innerArgs) and combines them with the existing ones.
  3. Sum Function: The sum function calculates the sum of all accumulated arguments using Array.prototype.reduce.
  4. Custom toString and valueOf Methods: These methods ensure that when the curried function is converted to a string or a number (e.g., when logging or coercing to a primitive), it returns the computed sum.
  5. Return Curried Function: The currySum function returns the add function with the accumulated arguments, allowing for further chaining of arguments.
  • This implementation ensures that the add function can be called in a curried manner and still compute the correct sum regardless of how the arguments are provided.