Skip to main content

5. 7 Summarizing with reduce

 Summarizing with reduce   Understanding Reduce Purpose of Reduce : reduce is a higher-order function used to compute a single value from an array. It's instrumental in operations like summing up numbers or finding an item in an array that meets a specific criterion (e.g., the script with the most characters). Operation : It works by repeatedly taking an element from the array and combining it with a current value until all elements have been processed. This process is akin to folding or reducing the array into a single value. 🎨: Think of reduce like making juice from oranges. You start with a bunch of oranges (the array), and then you squeeze them one by one into a jug (the single value). Your hands (the combining function) do the squeezing, and you might already have some juice in the jug to start with (the start value). πŸ‘©‍πŸŽ“: The Mechanics of reduce - The reduce function takes three arguments: the array to reduce, a combining function, and a starting value. The combin...

5.3 Higher-order functions

 5.3 Higher-order functions

 

 
This paragraph  introduces the concept of higher-order functions in programming, specifically within the context of JavaScript. Higher-order functions are a powerful aspect of functional programming that allow you to write more abstract, concise, and reusable code. Let's break down the examples provided to clarify their meanings and uses.
 
 

Higher-order Functions Overview

  • Definition: Functions that take other functions as arguments or return them as results are called higher-order functions. This capability is not unique or unusual in programming, especially in languages like JavaScript where functions are treated as first-class citizens, meaning they can be passed around just like other values (numbers, strings, etc.).

Examples Explained

1. Creating New Functions


function greaterThan(n) {
  return m => m > n;
}
let greaterThan10 = greaterThan(10);
console.log(greaterThan10(11)); // → true

  
  • greaterThan(n) is a higher-order function that takes a number n and returns a new function. This new function takes another number m and checks if it's greater than n.
  • greaterThan10 becomes a function that checks if a number is greater than 10. When we call greaterThan10(11), it returns true because 11 is indeed greater than 10.

The code defines a function named greaterThan that takes one parameter, n. This function, when called, doesn't directly return a boolean value or perform any action with n. Instead, it returns a new function. This inner function takes a single parameter, m, and checks if m is greater than n.

Breaking Down the Functionality

  1. Function Creation: When you call greaterThan(10), you're creating a new function. This new function is designed to take one argument (m) and return true if m is greater than 10. This is because the n inside greaterThan has been set to 10.

  2. Function Storing: The new function is stored in the variable greaterThan10. This doesn't execute the function; it merely stores the function for later use.

  3. Function Execution: When you later call greaterThan10(11), you're now executing the function stored in greaterThan10, passing 11 as the argument. Since 11 is greater than 10, the comparison m > n (or 11 > 10) evaluates to true.

Why Does It Work This Way?

This code utilizes a concept called "closure" in JavaScript. A closure is a function that remembers the variables from the place where it was defined, regardless of where it is executed. So, when greaterThan(10) creates and returns a new function, that new function remembers the value of n as 10.

Visualizing the Process

  1. Call greaterThan with 10 ➔ Creates a function that checks if a given number is > 10.
  2. Store that function in greaterThan10.
  3. Call greaterThan10 with 11 ➔ Since 11 is greater than 10, it returns true.

Key Takeaway

Your code is an example of higher-order functions and closures. A higher-order function is a function that can return another function or take a function as an argument. Closures allow a function to access variables from its lexical scope, enabling powerful patterns like this one.

This example is fundamental in understanding how closures and higher-order functions can be used to create very flexible and reusable pieces of code.

πŸ§‘ I need more explanations. Let's start again! It is really important to understand where come m from as it is not explicitely defined as a parameter.

🧠 OK.

  1. First Call Transformation: When you call greaterThan(10), the function greaterThan doesn't modify the arrow function on the fly from return m => m > n; to return m => m > 10; in the source code. Instead, what happens is conceptually similar but occurs at runtime due to how closures work. The n in the arrow function m => m > n becomes bound to the value 10 because of the closure created when greaterThan(10) is executed. This means that anytime this returned function (now stored in greaterThan10) is called, it uses 10 as the value for n.

  2. Execution with the Stored Function: When you later call greaterThan10(11), you are indeed calling the arrow function m => m > 10; with m being 11. It's because the function stored in greaterThan10 remembers the value of n as 10 from when it was created. This is a direct consequence of closures, which allow the function to "remember" the environment in which it was created.

Clarifying the Concept:

  • The original function, greaterThan, acts like a factory that produces new functions. Each new function it produces is configured to compare against the n value provided when it was called.
  • The produced function (stored in greaterThan10) doesn't get physically rewritten in your code; it's more about how it behaves at runtime. The effect is as if it has been transformed because the n parameter is fixed to the value provided at the time of the original function's call.

In Simple Terms:

  • Creating: greaterThan(10) creates a new function that looks out for any number m and checks if m is greater than 10.
  • Storing: This new function is stored in greaterThan10. It's ready to use, always comparing against 10.
  • Using: When you call greaterThan10(11), you're using this stored function to check if 11 is greater than 10.

This beautifully illustrates the power of closures in JavaScript, enabling functions to carry with them some "memory" of the environment in which they were created.

 

πŸ’‘ Exactly! When you assign greaterThan10 = greaterThan(10), the variable greaterThan10 is storing the arrow function m => m > n, not the original greaterThan function. The crucial part to understand here is that the n in the arrow function is already set to 10 because of how closures work. This means greaterThan10 becomes a specific version of the arrow function that always compares its argument m to 10.

Understanding Closure with Your Example:

  1. Original Function Call: When you call greaterThan(10), you're essentially creating a new context in which n is 10.

  2. Arrow Function: The arrow function m => m > n is returned. This function is "remembering" the value of n from its creation context, which is 10.

  3. Stored in Variable: The returned arrow function is stored in the variable greaterThan10. At this point, greaterThan10 is a function that takes a single parameter m and compares it to 10.

  4. Closure in Action: The ability of greaterThan10 to remember the value of n as 10 is due to closures. This is why we say that greaterThan10 is the arrow function with n permanently set to 10.

Visual Analogy: Think of greaterThan(10) as creating a custom tool (the arrow function) designed specifically to compare numbers to 10. When you store it in greaterThan10, you're not storing the tool-making machine (greaterThan function) but the custom tool itself. This tool is ready to be used to compare any number to 10.

In Summary: The greaterThan10 variable doesn't hold the original greaterThan function but a new function that resulted from calling greaterThan with 10. This new function is ready to check if any given number is greater than 10, showcasing a practical use of closures and higher-order functions in JavaScript.

 

2.  Modifying Functions


function noisy(f) {
  return (...args) => {
    console.log("calling with", args);
    let result = f(...args);
    console.log("called with", args, ", returned", result);
    return result;
  };
}
noisy(Math.min)(3, 2, 1);

  
  • noisy(f) is a higher-order function that takes a function f and returns a new function. This new function logs a message before and after calling f, providing insight into how f is called and what it returns.
  • When noisy(Math.min) is called with (3, 2, 1), it logs the arguments and the result of Math.min(3, 2, 1), which is 1.

 πŸ§‘ I need detailed breakdown:I do not understand this return (...args)

The return (...args) syntax you're asking about is used in JavaScript to define a function that can accept an arbitrary number of arguments. This is achieved using the rest parameter syntax ..., which gathers all the remaining arguments passed to the function into an array. Let's break down this concept for clarity:

Rest Parameters Syntax (...args)

  • Purpose: The rest parameter syntax allows a function to accept an indefinite number of arguments as an array, providing a way to capture all arguments passed to the function beyond those explicitly defined.
  • Syntax: The ... (three dots) before a parameter name signifies that this parameter will be an array containing all remaining arguments passed to the function.

In the Context of the noisy Function

When used in the return (...args) within the noisy function example:

 

  • What it does: This defines an anonymous function (using arrow function syntax) that can take any number of arguments. The ...args collects these arguments into an array named args.
  • How it works:
    • When this anonymous function is called, all arguments passed to it are gathered into the args array.
    • It then logs "calling with", followed by the arguments array, to the console.
    • f(...args) calls the original function f with all of the arguments spread out. This is the spread syntax, which does the opposite of rest parameters: it spreads the elements of an array out into individual arguments.
    • After calling f with the given arguments, it logs "called with", the arguments, and the result of the function call.
    • Finally, it returns the result of calling f with the provided arguments.

Example Usage

When noisy(Math.min)(3, 2, 1) is executed:

  1. noisy(Math.min) returns a new function that wraps Math.min with additional logging.
  2. This new function is immediately called with the arguments (3, 2, 1).
  3. Inside this function, ...args collects the arguments into an array, so args is equivalent to [3, 2, 1].
  4. Math.min(...args) spreads the args array back into individual arguments, effectively calling Math.min(3, 2, 1).
  5. The function logs the arguments and the result (1) to the console.

Summary

The return (...args) pattern is powerful for creating flexible functions that can handle an arbitrary number of arguments. It's particularly useful in higher-order functions that need to operate on other functions without knowing in advance how many arguments those functions will require.

 

3.  New Types of Control Flow


function unless(test, then) {
  if (!test) then();
}

repeat(3, n => {
  unless(n % 2 == 1, () => {
    console.log(n, "is even");
  });
});

  
  • unless is a function that takes a condition test and a function then. If test is false, it executes then.
  • The repeat function (assumed to be defined elsewhere) iterates 3 times, and for each iteration, unless checks if the current number n is even. If so, it logs that n is even. 

  πŸ§‘ I need detailed breakdown.

  πŸ¦† Please explain the function to me!

πŸ‘©‍πŸŽ“ Here's how you'd explain this function to your rubber duck:

  1. Purpose of unless:

    • "Hey, Duck πŸ¦†! This function, unless, decides whether or not to run some code based on a condition. It's like saying, 'do this thing unless some condition is true.'"
  2. Parameters:

    • "test is our condition. It's a boolean that tells us whether the 'then' part should run or not."
    • "then is a function. It's what we'll do if the test condition isn't met. Think of it as our 'otherwise' action."
  3. Function Body:

    • "Inside unless, there's an if statement checking !test, which means 'if not test'. So, if test is false, it's our cue to do something."
    • "What do we do if test is false? We call the then function. That's what then() is about. It says, 'Hey, our condition wasn't true, so let's do the then action.'"
  4. How it Works in Practice:

    • "Imagine we're using unless to log numbers that are even. Our test would check if a number is odd (n % 2 == 1)."
    • "We only want to log if the number is even, so our 'unless test is true (the number is odd), do this' logic fits perfectly."

Applying unless with repeat

Explaining this usage:

  1. Repeat Function Context:

    • "We have another function, repeat, that runs a piece of code multiple times. Here, it's set to run 3 times, with n going from 0 to 2."
  2. Using unless Inside repeat:

    • "For each iteration, we call unless with a test (n % 2 == 1) that checks if n is odd."
    • "The then part is a function that logs n with a message saying it's even, but it only runs if our test (the number is odd) is false."
  3. The Result:

    • "So, Duck, for each number from 0 to 2, unless checks if it's odd. If it's not (meaning it's even), it logs that number with 'is even'. That's why we see '0 is even' and '2 is even' logged out."

Conclusion

"By using unless, Duck, we've abstracted a common pattern: doing something only when a certain condition is not met. It helps make our intentions clear and our code more readable. And that's pretty neat, right?"

This rubber duck method helps break down the function into understandable pieces, clarifying its purpose and how it works in a simple, conversational manner.

4. Built-in Higher-order Functions

 


["A", "B"].forEach(l => console.log(l));

  
  • .forEach is a built-in JavaScript array method that is a higher-order function. It takes a function as an argument and applies that function to every element in the array.
  • In this case, it logs each letter in the array to the console.

  πŸ¦† Please explain the function to me!

 

Let's tackle the ["A", "B"].forEach(l => console.log(l)); line with the rubber duck method. This line involves using the forEach method on an array, so let's explain it piece by piece to our rubber duck friend.

The forEach Method Explained

"Alright, Duck, let's look at this piece of JavaScript code that uses .forEach. It's a method that arrays have, which allows us to do something with each element in the array."

Understanding ["A", "B"].forEach(l => console.log(l));

  1. The Array:

    • "First, we have an array with two elements, ['A', 'B']. It's a simple list with two strings in it."
  2. The forEach Method:

    • "forEach is a method we can call on our array. What it does is go through each element in the array, one by one. Think of it as walking down a line of ducks, where you give a nod to each duck as you pass."
  3. The Function Passed to forEach:

    • "Inside the parentheses of forEach, we're passing a function: (l => console.log(l)). This is an arrow function, a concise way to write functions in JavaScript."
    • "The l is a parameter for our arrow function. It represents the current element of the array that forEach is looking at. So as forEach moves through the array, l becomes each element in turn. It's not the array itself but each item inside the array, one at a time."
    • "For each element in the array, we're telling JavaScript to console.log(l), which means 'print this element to the console.' So, when l is 'A', it prints 'A'. When l is 'B', it prints 'B'."
  4. Execution Flow:

    • "So, Duck, when we run ['A', 'B'].forEach(l => console.log(l));, here's what happens: For each element in the array (first 'A', then 'B'), the function we provided to forEach is called with that element. That function takes the element (l) and logs it to the console."
  5. Outcome:

    • "The outcome of running this line is that we see 'A' and 'B' printed on the console, each on a new line. It's a way of saying 'hello' to each element in the array."

Summary

"In summary, Duck, the l in our function isn't the array itself but a placeholder for each element of the array as forEach goes through it. forEach is a handy way to do something with each item in an array, like greeting each duck in a row. And that's what we're doing here with our letters 'A' and 'B'."

This explanation breaks down how .forEach works and what the variable l represents, making it clearer how this method operates on each element of an array in a very straightforward way.

 

Conclusion

Higher-order functions are a core concept in functional programming, providing a powerful way to abstract operations (actions) and not just values. They enable you to write more general, reusable code patterns, such as creating new functions based on input, wrapping functions with additional behavior, implementing custom control flows, and working with built-in methods like .forEach for arrays.

Understanding higher-order functions can greatly enhance your ability to think about and solve problems in a functional style, making your code more expressive and concise.

 

πŸ§‘ Dummy Boy ANNEX


πŸ§‘: I still do not really understand return (...args) that spreads out into array the arguments and f(...args) that calls the original function f with all of the arguments spread out.

 

It seems there's a bit of confusion regarding the ... syntax used in JavaScript functions, so let's clarify the distinction between return (...args) and f(...args) to ensure the concept is clear.

The Rest Parameter: (...args)

  • Purpose: The ... syntax, when used in function parameters (e.g., (...args)), is known as the rest parameter syntax. It allows you to represent an indefinite number of arguments as an array.
  • Example Use: In a function definition, function example(...args), ...args collects all remaining arguments passed to the function into an array named args.
  • Key Point: The rest parameter collects all arguments passed to the function into an array, making it easy to work with an unknown number of arguments.

The Spread Syntax: f(...args)

  • Purpose: The same ... syntax, when used in a function call (e.g., f(...args)), acts as the spread syntax. It allows an iterable such as an array or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected.
  • Example Use: If you have args as an array and you call a function like f(...args), you're spreading the elements of args as individual arguments to the function f.
  • Key Point: The spread syntax expands an array into individual elements, so if args is [1, 2, 3], calling f(...args) is equivalent to calling f(1, 2, 3).

Understanding the Distinction

In Function Definitions ((...args)): The rest parameter syntax collects all additional arguments passed to the function into an array, making it easier to handle a variable number of arguments.


function summarizeNumbers(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}  

 

"Hello, little Duck! Today, we're going to talk about a neat way to add up numbers using a function called 

summarizeNumbers

This isn't just any function; it's a special one that can take as many numbers as we want to give it. Let's see how it works, step by step."

 πŸ¦†

  1. Gathering the Numbers:

    • "First off, Duck, notice the ...numbers in our function's parameters? That's like telling the function, 'Hey, you might get a bunch of numbers thrown at you. Please catch them all and put them into a basket called numbers.' In technical terms, ...numbers is using the rest parameter syntax to collect all the arguments into an array so we can work with them more easily."
  2. Adding the Numbers Together:

    • "Next, we have this line: return numbers.reduce((total, n) => total + n, 0);. This might look a bit complex, but it's actually pretty straightforward. We're using something called the reduce method, which is a way to boil down all the elements in our numbers basket into a single value."
    • "Think of reduce as a little machine that goes through each number in the basket, one by one. It takes a running total (total) and the current number (n), adds them together, and then carries this new total to the next number until it has visited all the numbers. The 0 at the end is just telling our machine where to start counting from."
  3. The Result:

    • "After reduce has visited all the numbers, it gives us the total sum. That's the number we get back when we call summarizeNumbers(1, 2, 3, 4), for example. It adds up 1 + 2 + 3 + 4, and returns 10."
  4. Why It's Cool:

    • "What's really nice about this summarizeNumbers function, Duck, is that we can give it any number of arguments, like summarizeNumbers (1, 2) or summarizeNumbers(1, 2, 3, 4, 5, 6), and it will happily add them all up for us. No need to tell it how many numbers are coming; it just figures it out and does its job."

"And there you have it, Duck! A simple, flexible way to add numbers together using a function in JavaScript. Pretty neat, huh?"

This explanation breaks down the function into understandable parts, making it clear how the summarizeNumbers function works to dynamically add any number of arguments together using JavaScript's rest parameters and the reduce method. 

πŸ§‘ It is not clear at all though???

πŸ€– This code snippet  uses the reduce method to calculate the sum of all the elements in an array. Let's break it down step by step for clarity:

  1. The Array: const array = [1, 2, 3, 4];
    This line creates an array named array that contains four elements: 1, 2, 3, and 4.

  2. The reduce Method: const sum = array.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
    This line calculates the sum of all the elements in the array using the reduce method. The reduce method takes two parameters: a reducer function and an initial value for the accumulator.

    • Reducer Function: (accumulator, currentValue) => accumulator + currentValue
      This function is called for each element in the array. It takes two arguments: accumulator and currentValue.
      • accumulator is a variable that accumulates the return values of the function. It's the accumulated value previously returned in the last invocation of the callback, or the initial value if supplied.
      • currentValue is the current element being processed in the array.
    • Initial Value: , 0
      This is the second parameter to the reduce method, and it sets the initial value of the accumulator. In this case, it's set to 0.

How reduce Works in This Example:
The reduce method works by iterating over each element in the array, starting from the first element if no initial value is provided, or starting with the initial value if one is provided. For your example, the process looks like this:

  • First iteration: accumulator = 0 (initial value), currentValue = 1 (first element of the array), so it calculates 0 + 1 and returns 1.
  • Second iteration: accumulator = 1 (result of the first iteration), currentValue = 2, so it calculates 1 + 2 and returns 3.
  • Third iteration: accumulator = 3, currentValue = 3, so it calculates 3 + 3 and returns 6.
  • Fourth iteration: accumulator = 6, currentValue = 4, so it calculates 6 + 4 and returns 10.

After the final iteration, the reduce method returns the total sum of the array elements, which is 10 in this case. This final sum is then assigned to the sum variable.

So, the reduce method in your code is a powerful way to iterate through an array and reduce its elements to a single value, which, in this scenario, is the sum of all its elements.

 

πŸ§‘ But why the accumulator accumulates the value?

 

πŸ€–The accumulator in the reduce method accumulates value because that's its purpose within the function—it serves as a "collector" that aggregates or combines values according to a specific operation defined in the reducer function. The reason why the accumulator accumulates value over each iteration is due to how the reduce method is designed to work:

  1. Initial Value: The process starts with an initial value. If an initial value is provided in the reduce method call (like the 0 in your example), that becomes the starting value of the accumulator. If no initial value is provided, the first element of the array becomes the initial accumulator value, and the iteration starts from the second element.

  2. Reducer Function: This function specifies how the accumulated value should be updated with each iteration over the array elements. In your example, the reducer function adds the current value of the array element to the accumulator (accumulator + currentValue).

  3. Iteration Process: During each iteration, the reduce method applies the reducer function to the current accumulator value and the current array element, producing a new accumulator value. This process is repeated for each element in the array.

  4. Accumulation Logic: The reason the accumulator accumulates value is because, with each call to the reducer function, the result of the operation (accumulator + currentValue in your case) is passed on as the accumulator value for the next iteration. This means the result of each operation is not discarded but instead built upon, accumulating the desired result step by step. In mathematical operations like summing, this effectively adds up all the values in the array.

  5. Final Value: After the last iteration, the accumulated value becomes the final result of the reduce method, which in your example is the sum of all elements in the array.

The accumulation process is a powerful aspect of the reduce method, allowing it to perform not just addition, but any operation that combines all elements of an array into a single value. This could include multiplication, finding the maximum value, or even more complex operations like building up a new object or array based on the elements of the input array.

πŸ§‘But there is not explitely stated that there is summation it may be multiplication or division how I would know?

Understanding whether the operation within the reduce method will be summation, multiplication, division, or any other operation depends entirely on the implementation of the reducer function provided to it. The operation is determined by how you define the reducer function. Let's clarify this using your example and extend it to multiplication and division to illustrate the differences:

In this example, the + operator explicitly indicates that the operation is summation. The accumulator accumulates the sum because each time the reducer function is called, it adds the current array element (currentValue) to the running total (accumulator).

 

Multiplication Example

To perform multiplication instead of addition, you would change the operation in the reducer function and adjust the initial value to 1 (since multiplying by 0 would result in 0 for any array). Here's how it looks:

 const product = array.reduce((accumulator, currentValue) => accumulator * currentValue, 1);

In this multiplication example, the * operator is used, indicating that the operation is multiplication. The initial value is 1 because that's the neutral element in multiplication (anything multiplied by 1 remains unchanged).

Division Example

Division is less common in a reduce operation because it's not associative in the same way as addition and multiplication (the order of division matters), but here's a conceptual example for educational purposes:

 const divisionResult = array.reduce((accumulator, currentValue) => accumulator / currentValue, initialAccumulatorValue);

In a division operation, the choice of the initial accumulator value (initialAccumulatorValue) is crucial and depends on what you're trying to achieve. Division operations with reduce can lead to unexpected results if not carefully planned, especially considering the order of operations and the impact of starting with an appropriate initial value.

How to Know Which Operation Is Being Performed

  • Look at the Operator: The mathematical operator (+, -, *, /) used in the reducer function explicitly indicates the type of operation being performed.
  • Initial Value: The initial value can also give a hint. For summation, 0 is a common initial value; for multiplication, 1 is used.
  • Understanding the Code: Knowing the intended result by reading the code or documentation comments can clarify the operation's purpose. For example, variable names like sum or product can indicate the intended operation.

In summary, the specific operation performed by the reduce method is defined by how you implement the reducer function. You control whether the accumulator sums, multiplies, divides, or does something else entirely by the logic you write in this function.

 

 



In Function Calls (f(...args))

The spread syntax is used to pass the elements of an array as individual arguments to a function. This is useful when you already have an array and want to use its values as arguments to a function.

let values = [1, 2, 3];
let max = Math.max(...values); // Equivalent to Math.max(1, 2, 3)

  

Conclusion

The key distinction lies in how ... is used: as a rest parameter in function definitions to collect arguments into an array, and as a spread syntax in function calls to spread an array's elements as individual arguments. Understanding this difference is crucial for effectively utilizing JavaScript's flexibility in handling functions and arguments.

Comments