5.3 Higher-order functions
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 numbernand returns a new function. This new function takes another numbermand checks if it's greater thann.greaterThan10becomes a function that checks if a number is greater than 10. When we callgreaterThan10(11), it returnstruebecause 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
Function Creation: When you call
greaterThan(10), you're creating a new function. This new function is designed to take one argument (m) and returntrueifmis greater than 10. This is because theninsidegreaterThanhas been set to 10.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.Function Execution: When you later call
greaterThan10(11), you're now executing the function stored ingreaterThan10, passing11as the argument. Since11is greater than10, the comparisonm > n(or11 > 10) evaluates totrue.
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
- Call
greaterThanwith10➔ Creates a function that checks if a given number is > 10. - Store that function in
greaterThan10. - Call
greaterThan10with11➔ Since11is greater than10, it returnstrue.
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.
First Call Transformation: When you call
greaterThan(10), the functiongreaterThandoesn't modify the arrow function on the fly fromreturn m => m > n;toreturn m => m > 10;in the source code. Instead, what happens is conceptually similar but occurs at runtime due to how closures work. Thenin the arrow functionm => m > nbecomes bound to the value10because of the closure created whengreaterThan(10)is executed. This means that anytime this returned function (now stored ingreaterThan10) is called, it uses10as the value forn.Execution with the Stored Function: When you later call
greaterThan10(11), you are indeed calling the arrow functionm => m > 10;withmbeing11. It's because the function stored ingreaterThan10remembers the value ofnas10from 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 thenvalue 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 thenparameter 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 numbermand checks ifmis greater than10. - Storing: This new function is stored in
greaterThan10. It's ready to use, always comparing against10. - Using: When you call
greaterThan10(11), you're using this stored function to check if11is greater than10.
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.
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 functionfand returns a new function. This new function logs a message before and after callingf, providing insight into howfis called and what it returns.- When
noisy(Math.min)is called with(3, 2, 1), it logs the arguments and the result ofMath.min(3, 2, 1), which is1.
π§ 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
...argscollects these arguments into an array namedargs. - How it works:
- When this anonymous function is called, all arguments passed to it are gathered into the
argsarray. - It then logs
"calling with", followed by the arguments array, to the console. f(...args)calls the original functionfwith 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
fwith the given arguments, it logs"called with", the arguments, and the result of the function call. - Finally, it returns the result of calling
fwith the provided arguments.
- When this anonymous function is called, all arguments passed to it are gathered into the
Example Usage
When noisy(Math.min)(3, 2, 1) is executed:
noisy(Math.min)returns a new function that wrapsMath.minwith additional logging.- This new function is immediately called with the arguments
(3, 2, 1). - Inside this function,
...argscollects the arguments into an array, soargsis equivalent to[3, 2, 1]. Math.min(...args)spreads theargsarray back into individual arguments, effectively callingMath.min(3, 2, 1).- 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");
});
});
unlessis a function that takes a conditiontestand a functionthen. Iftestis false, it executesthen.- The
repeatfunction (assumed to be defined elsewhere) iterates 3 times, and for each iteration,unlesschecks if the current numbernis even. If so, it logs thatnis even.
π§ I need detailed breakdown.
π¦ Please explain the function to me!
π©π Here's how you'd explain this function to your rubber duck:
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.'"
- "Hey, Duck π¦! This function,
Parameters:
- "
testis our condition. It's a boolean that tells us whether the 'then' part should run or not." - "
thenis a function. It's what we'll do if thetestcondition isn't met. Think of it as our 'otherwise' action."
- "
Function Body:
- "Inside
unless, there's anifstatement checking!test, which means 'if not test'. So, iftestis false, it's our cue to do something." - "What do we do if
testis false? We call thethenfunction. That's whatthen()is about. It says, 'Hey, our condition wasn't true, so let's do thethenaction.'"
- "Inside
How it Works in Practice:
- "Imagine we're using
unlessto log numbers that are even. Ourtestwould 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."
- "Imagine we're using
Applying unless with repeat
Explaining this usage:
Repeat Function Context:
- "We have another function,
repeat, that runs a piece of code multiple times. Here, it's set to run 3 times, withngoing from 0 to 2."
- "We have another function,
Using
unlessInsiderepeat:- "For each iteration, we call
unlesswith a test (n % 2 == 1) that checks ifnis odd." - "The
thenpart is a function that logsnwith a message saying it's even, but it only runs if ourtest(the number is odd) is false."
- "For each iteration, we call
The Result:
- "So, Duck, for each number from 0 to 2,
unlesschecks 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."
- "So, Duck, for each number from 0 to 2,
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));
.forEachis 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!
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),...argscollects all remaining arguments passed to the function into an array namedargs. - 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
argsas an array and you call a function likef(...args), you're spreading the elements ofargsas individual arguments to the functionf. - Key Point: The spread syntax expands an array into individual elements, so if
argsis[1, 2, 3], callingf(...args)is equivalent to callingf(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
summarizeNumbersThis 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."
π¦
Gathering the Numbers:
- "First off, Duck, notice the
...numbersin 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 callednumbers.' In technical terms,...numbersis using the rest parameter syntax to collect all the arguments into an array so we can work with them more easily."
- "First off, Duck, notice the
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 thereducemethod, which is a way to boil down all the elements in ournumbersbasket into a single value." - "Think of
reduceas 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. The0at the end is just telling our machine where to start counting from."
- "Next, we have this line:
The Result:
- "After
reducehas visited all the numbers, it gives us the total sum. That's the number we get back when we callsummarizeNumbers(1, 2, 3, 4), for example. It adds up 1 + 2 + 3 + 4, and returns10."
- "After
Why It's Cool:
- "What's really nice about this
summarizeNumbersfunction, 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."
- "What's really nice about this
"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???
π§ But why the accumulator accumulates the value?
π§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,
0is a common initial value; for multiplication,1is 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
sumorproductcan 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)):
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
Post a Comment