Abstracting repetition
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
} Parameter
n: This is the number of times you want to repeat an action. It's like saying, "I want to do this thingntimes."Parameter
action: This is a function that represents the action you want to perform each time. Instead of hardcoding what happens inside the loop (like logging numbers), you pass a function that does whatever you want.The Loop: Inside
repeat, aforloop runs from0ton-1. Each iteration of the loop calls theactionfunction, passing the current index (i) to it.Simplified Example: Using
repeatto Log NumbersLet's take the simplest use case: logging numbers from
0ton-1. Here's how you'd do it withrepeat
repeat(3, console.log);
This tells the program to "repeat the action of logging to the console 3 times." The action here is console.log, which is a built-in JavaScript function for printing output.
Breaking Down the Action Function
repeat. Here’s a basic example to illustrate this:let numbers = [];
repeat(3, i => {
numbers.push(i);
});
console.log(numbers); // → [0, 1, 2]In this example:
- We create an array
numbersto hold our values. - We define a function on the spot (
i => { numbers.push(i); }) that takes an indexiand pushes it into thenumbersarray. This is theactionwe want to repeat. - We call
repeat(3, action), telling it to execute our function 3 times, each time with the current index as its argument.
Key Takeaway
The power of the repeat function lies in its ability to perform any action multiple times, where the action is defined by you through a function. This makes your code more abstract (less tied to specific tasks) and reusable.
π©π: Think of repeat as a tool that says, "Tell me how many times and what you want to do, and I'll take care of the rest." It's a way to apply the DRY (Don't Repeat Yourself) principle, avoiding repetitive code and making your programs more modular and maintainable.
repeat function call you mentioned step by step, especially focusing on the i + 1 part to understand how it contributes to numbering:let labels = [];
repeat(5, i => {
labels.push(`Unit ${i + 1}`);
});
Understanding the repeat Function Structure
The
repeatFunction Call: Here,repeatis called with two arguments:5: This indicates the number of times the action should be repeated.i => { labels.push(Unit ${i + 1}); }: This is an arrow function (a concise way to write functions in JavaScript) that defines the action to be repeated. This function takes a parameteri, which is the current iteration index (starting from 0).
Action to be Repeated: Inside the repeat function, for each iteration (from
0to4, since we're repeating5times), the arrow function is executed. The current iteration indexiis passed to this function.
The Role of i + 1 in Numbering
- Iteration Index (
i): The for loop insiderepeatiteratesntimes (5 times in our case). For each iteration,istarts at0and increments by1until it reaches4(n-1). - Using
i + 1: When you pushUnit ${i + 1}into thelabelsarray, you're creating a string that includes the current iteration index plus one. This adjustment (i + 1) is done because indices in programming generally start at0, but for human-readable labels or numbering, you often want to start counting from1. - Creating Numbering: As a result,
i + 1translates the computer's way of counting (starting at 0) into a more natural numbering sequence (starting at 1). This means, for each iteration, you getUnit 1,Unit 2, up toUnit 5instead of starting fromUnit 0.
Detailed Breakdown of Each Iteration
- First Iteration (
i = 0): The function pushesUnit 1into thelabelsarray. - Second Iteration (
i = 1): The function pushesUnit 2into thelabelsarray. - Third Iteration (
i = 2): The function pushesUnit 3into thelabelsarray. - Fourth Iteration (
i = 3): The function pushesUnit 4into thelabelsarray. - Fifth Iteration (
i = 4): The function pushesUnit 5into thelabelsarray.
Final Outcome
After the loop completes, labels contains ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"], effectively creating a series of numbered labels starting from 1 up to the number of repetitions specified (in this case, 5).
π©π: The usage of i + 1 here is a simple yet clever way to bridge the gap between zero-based indexing (a common convention in programming) and the more intuitive one-based numbering that we use in everyday contexts.
⚙️: This breakdown clarifies how combining simple concepts like iteration indexes and array operations with function parameters can create powerful and flexible code structures. Do you have any further questions on this or any other topic?
π§Let's do some more advanced examples
⚙️: Let's dive deeper into the concept of abstracting repetition by exploring variations of the repeat function and applying it to different scenarios. This will showcase the versatility and power of higher-order functions in making your code more flexible and expressive.
Example 1: Performing Operations on Array Elements
Consider a scenario where you want to apply a specific operation to each element in an array and collect the results. Using our repeat abstraction, we can easily achieve this.
function mapArray(array, transform) {
let result = [];
repeat(array.length, i => {
result.push(transform(array[i]));
});
return result;
}
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = mapArray(numbers, x => x * x);
console.log(squaredNumbers); // → [1, 4, 9, 16, 25]
Let's dive into the annotated version of the mapArray function example to ensure you get a clear understanding of how it works and how it leverages the concept of abstracting repetition.
Annotated mapArray Function
// Define the mapArray function with two parameters: 'array' and
// 'transform'.
// 'array' is the array you want to transform.
// 'transform' is the function that defines how each element
//in the array should be transformed.
function mapArray(array, transform) {
// Initialize an empty array 'result' to store
//the transformed elements.
let result = [];
// Use the 'repeat' function to iterate over each element in
//the 'array'. The 'repeat' function takes two arguments:
// the number of times to repeat
// (array.length) and an action function that defines what
// to do in each iteration.
repeat(array.length, i => {
// For each iteration, apply the 'transform' function to
//the current element (array[i])
// and push the transformed element into the 'result' array.
result.push(transform(array[i]));
});
// After iterating through all elements, return the 'result'
// array containing the transformed elements.
return result;
}
// Example usage of mapArray function:
// Define an array of numbers.
const numbers = [1, 2, 3, 4, 5];
// Call the 'mapArray' function, passing in the 'numbers'
//array and an arrow function 'x => x * x' that describes how
// to transform each element (in this case, squaring it).
const squaredNumbers = mapArray(numbers, x => x * x);
// Log the 'squaredNumbers' array to the console to see the
// transformation result.
console.log(squaredNumbers); // → [1, 4, 9, 16, 25]
Key Concepts Illustrated
Abstraction: The
mapArrayfunction abstracts the process of transforming each element in an array according to a specific rule (defined by thetransformfunction). This is a practical demonstration of how higher-order functions can abstract common tasks like iteration and application of a transformation.Higher-order Functions: Both
mapArrayand thetransformfunction it accepts as an argument are examples of higher-order functions.mapArraytakes a function as an argument (transform), andtransformis a function that gets applied to each element of the array.Functional Programming: This example embodies functional programming principles by using functions as first-class citizens (passing functions as arguments) and focusing on immutability and statelessness (
resultarray is created anew and returned without modifying the originalarray).Using
repeatfor Iteration: The example leverages the customrepeatfunction to iterate over the array, showcasing how custom iteration mechanisms can replace traditional loops likefororforEachfor more abstracted and flexible code structures.
mapArray function operates and its underlying principles.
Example 2: Filtering Array Elements
Similarly, we can abstract the concept of filtering elements of an array based on a predicate (a function that returns true or false).
Let's break down the filterArray function and its usage
with a "rubber duck" style explanation, keeping the annotations concise
and directly below the code for clarity.
// Define filterArray with 'array' to filter and 'test' as
//the condition to meet.
function filterArray(array, test) {
// Initialize an empty array to hold elements that
//pass the test.
let result = [];
// Use repeat to go over each element in the input array.
repeat(array.length, i => {
// If the current element passes the test, add it to the
//result array.
if (test(array[i])) {
result.push(array[i]);
}
});
// Return the array with elements that passed the test.
return result;
}
Usage Example
// An array of numbers to filter.
const numbers = [1, 2, 3, 4, 5];
// Call filterArray, passing 'numbers' and a function to test
//evenness.
const evenNumbers = filterArray(numbers, x => x % 2 === 0);
// Log the filtered array, containing only even numbers.
console.log(evenNumbers); // → [2, 4] Explanation
filterArrayFunction: It creates a new array by going through each element of the input array (array) and applying a test (test). If an element passes the test (i.e., the test function returnstruefor that element), it's added to a new array (result). This process filters the input array based on any condition defined in thetestfunction.repeatUsage: Instead of a traditional for loop,repeatabstracts the iteration process. It repeats an action (in this case, the test and potential addition to the result array) for each element in the input array.Usage with
numbersArray: WhenfilterArrayis called withnumbersand a test for evenness (x => x % 2 === 0), it filters out only the even numbers from thenumbersarray and stores them inevenNumbers.Result:
evenNumberswill contain elements fromnumbersthat satisfy the conditionx % 2 === 0, which are[2, 4]for this specific example.
This "rubber duck" explanation aims to clarify the purpose and function of filterArray by focusing on what each part of the code does in a straightforward manner.
Example 3: Generating HTML Elements
Abstraction can also be applied to more creative tasks, such as dynamically generating HTML elements based on a set number of repetitions.
function createListItems(n, contentFunction) {
let items = [];
repeat(n, i => {
items.push(`${contentFunction(i)} `);
});
return items.join('');
}
let listHTML = createListItems(5, i => `Item ${i + 1}`);
console.log(listHTML);
// → "Item 1 Item 2 Item 3 Item 4 Item 5 " Usage Example
// Define the number of list items to create and the content for each item.
let listHTML = createListItems(5, i => `Item ${i + 1}`);
// Log the resulting HTML string.
console.log(listHTML);
// Outputs: "Item 1 Item 2 Item 3 Item 4 Item 5 "
Explanation
createListItemsFunction: This function dynamically creates a list (<li>) HTML string based on a specified number (n) and a function (contentFunction) that defines what content goes inside each list item. It uses an array (items) to collect individual list items before combining them into a single HTML string.repeatUsage: Therepeatfunction is used to iteratentimes, creating a new list item on each iteration. The current index (i) is passed tocontentFunction, which returns the content for each list item. This approach abstracts the process of generating a list, allowing for dynamic content creation.Content Generation:
contentFunction(i)generates the content for each list item. In the usage example, it creates a stringItem ${i + 1}, which uses template literals to include the current index + 1 in the item text, effectively numbering the items starting from 1.Combining List Items: After generating all list items,
items.join('')combines them into a single string without any separators, ready to be used as HTML.Result: The function call
createListItems(5, i =>Item ${i + 1})generates a string representing a list of 5 numbered items, demonstrating how you can dynamically create HTML content based on simple inputs.
This detailed breakdown should help clarify how createListItems works to dynamically generate HTML list items based on given parameters and functions.
π©π: Each of these examples demonstrates how abstracting repetition into a function like repeat can significantly enhance the readability and modularity of your code. By passing different actions or predicates, you can easily customize the behavior of your loops without rewriting the looping logic itself.
π¨: And there's a beauty in how such a simple concept unfolds into endless possibilities, from crunching numbers to painting digital canvases of HTML. It's like having a skeleton key for loops, unlocking any door with a turn of the higher-order function.
⚙️: Exploring these examples, you can see how abstracting repetition not only simplifies your code but also opens up a world of programming patterns that are both elegant and powerful. This approach is at the heart of functional programming, emphasizing code that's concise, flexible, and expressive.
VOCABULARY: Functional programming
let count = 0;
function increment() {
count += 1; // Modifies the external variable 'count'.
}
Logging to the console:
function logMessage() {
console.log("Logging a message"); // Console output is a side effect.
}
Writing to a file:
Modifying a data structure in place:
const array = [1, 2, 3];
function addElement(element) {
array.push(element); // Changes the original array.
}
Fetching data from an API:
Pure Functions and Side Effects
In contrast, a pure function is one that does not cause any side effects. It guarantees that given the same input, it will always produce the same output, without modifying any state outside its scope or performing other observable actions. Pure functions are a cornerstone of functional programming because they offer predictability, are easier to test, and facilitate reasoning about the program flow.
Implications of Side Effects
While side effects are often necessary for a program to perform useful tasks (such as updating a user interface, sending a network request, or saving data), they can make code harder to understand and predict. This is because the outcome of a function call can depend on the context in which it's called, including the history of what other functions have been called previously.
Managing Side Effects: Functional programming aims to minimize uncontrolled side effects, often by isolating them, managing them through specific constructs (like monads in Haskell), or using immutable data structures and pure functions to ensure that the majority of the codebase remains easy to reason about and test.

Comments
Post a Comment