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 }
...
): Expands elements of an iterable (e.g., arrays, objects)....
): Collects multiple function arguments into an array.function*
and yield
): Functions that can pause and resume execution.{ next: function }
): Objects defining a sequence of values with a next
method.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
add
): This function takes any number of arguments using the rest parameter syntax (...args
).currySum
): This inner function is used to accumulate the arguments. It takes new arguments (...innerArgs
) and combines them with the existing ones.sum
function calculates the sum of all accumulated arguments using Array.prototype.reduce
.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.currySum
function returns the add
function with the accumulated arguments, allowing for further chaining of arguments.add
function can be called in a curried manner and still compute the correct sum regardless of how the arguments are provided.