Topic 009: Spread, Rest, Generators, Iterators, Currying
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}
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
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)
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 }
- Spread Operator (
...
): Expands elements of an iterable (e.g., arrays, objects). - Rest Parameter (
...
): Collects multiple function arguments into an array. - Generators (
function*
andyield
): Functions that can pause and resume execution. - Iterators (
{ next: function }
): Objects defining a sequence of values with anext
method.
These features provide powerful ways to handle collections, function arguments, and custom iteration behaviors in JavaScript.
- 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.
- 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.
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
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
- Base Function (
add
): This function takes any number of arguments using the rest parameter syntax (...args
). - Inner Function (
currySum
): This inner function is used to accumulate the arguments. It takes new arguments (...innerArgs
) and combines them with the existing ones. - Sum Function: The
sum
function calculates the sum of all accumulated arguments usingArray.prototype.reduce
. - Custom
toString
andvalueOf
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. - Return Curried Function: The
currySum
function returns theadd
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.