Chapter 10: First-class functions: Part 1
This introduction previews first-class functions and the code smell and refactorings the chapter will teach.
After this chapter you will be able to
- Implicit argument code smell. Describe how to recognize an implicit argument in a function name: similar functions differ by a value used in their bodies, and their names indicate that difference.
- Explicit argument refactoring. Trace the express-implicit-argument refactoring: identify the value in the name, add it as an argument, use that argument instead of the hard-coded value, and update callers.
- Limits of the cart API. Explain why marketing still needs the dev team despite having an API for the shopping cart. Describe how repeated requests to set different item fields expose the API’s limitation.
- Implicit argument code smell. Explain how to recognize an implicit argument in a function name in the cart-update functions: their implementations are very similar, and the field named in each function name determines the difference in its implementation.
- Expressing an implicit argument. Describe the steps of the express implicit argument refactoring and apply them to the section’s setPriceByName() example.
- First-class field names. Explain what it means for the field name to become a first-class value in this example.
- First-class values and limits. Explain what makes a value first-class in JavaScript and distinguish such values from operators and control-flow keywords.
- String arguments for name fragments. Describe how the earlier refactoring made an implicit part of a function name first-class by replacing it with a string argument.
- Validation of string field names. Distinguish compile-time and run-time checks for field names passed as strings, including when each check runs and how it can catch an invalid name.
- Stable field names and internals. Explain why accepting field names as strings does not require the API to keep the same internal field names.
- General entities as data. Explain why the book represents shopping carts and items with JavaScript objects and arrays rather than requiring a custom interface for each entity.
- Runtime interpretation at boundaries. Explain why string-based field names pose real error risks but why static types cannot eliminate the need to check data arriving across a system boundary.
- First-class arithmetic operators. Explain how wrapping an arithmetic operator in a function makes an equivalent first-class value, even though the operator itself cannot be assigned to a variable.
- Higher-order function relationship. Distinguish first-class functions from higher-order functions and explain why the former make the latter possible. Describe, at a conceptual level, how passing a function to another function relates to the proposed way to spare marketing from writing for loops.
- Shared array-loop refactoring. Trace how the eating and dishwashing loops become identical by wrapping them in functions, using more generic names, expressing the array as an argument, and extracting and passing the differing loop body as a function.
- Higher-order forEach abstraction. Explain why the resulting forEach() is a higher-order function and how passing it a function avoids rewriting the array loop for each kind of work.
- Replace body with callback. Describe how to identify the unchanged before and after code around a varying body, extract the whole structure into a function, and pass the body as a function argument.
- Inline anonymous function definitions. Explain what it means to define a function inline and anonymously, and describe when the section says that form is useful.
- Deferred execution with functions. Explain how wrapping saveUserData(user) in a function defers its execution and lets the wrapped code be passed to another function.
- Execution inside a reusable context. Explain why withLogging() receives a function rather than the result of calling saveUserData(user), and how that lets it run the code inside its try/catch.
What it builds on
- Pattern 2: Abstraction barrier (chapter 9) · The idea of an abstraction barrier between users of an API and its implementation.
- Abstraction barriers hide implementations (chapter 9) · The distinction between an abstraction barrier and the implementation it hides.
Section by section
Introduction
This introduction previews first-class functions and the code smell and refactorings the chapter will teach.
10.1 Code smell: Implicit argument in function name
Introduces a code smell that hides values in function names and outlines two refactorings that make values or behavior first-class.
- Implicit argument code smell: Describe how to recognize an implicit argument in a function name: similar functions differ by a value used in their bodies, and their names indicate that difference.
Why it matters: Spotting the hidden value shows where a function might be made more general. - Explicit argument refactoring: Trace the express-implicit-argument refactoring: identify the value in the name, add it as an argument, use that argument instead of the hard-coded value, and update callers.
Why it matters: Passing the differing value lets one function potentially serve cases that previously needed separate implementations. - Body replacement with callback: Describe how replacing a changing body with a callback lets callers pass behavior to an extracted function.
Why it matters: A callback lets the changing behavior be supplied without duplicating the surrounding code.
Key terms: code smell, implicit argument in function name, Express implicit argument, first-class, hard-coded value, Replace body with callback, before, body, and after, first-class function, higher-order functions
10.2 Marketing still needs to coordinate with dev
The section shows why the shopping-cart abstraction barrier has not given marketing the independence it was meant to provide.
- Limits of the cart API: Explain why marketing still needs the dev team despite having an API for the shopping cart. Describe how repeated requests to set different item fields expose the API’s limitation.
Why it matters: An API can hide a data structure yet still leave its users dependent on developers for routine changes.
Key terms: abstraction barrier, API, marketing team, dev team
10.3 Code smell: Implicit argument in function name
The section identifies a code smell in similar cart-update functions whose field names are embedded in their function names rather than passed as arguments.
- Implicit argument code smell: Explain how to recognize an implicit argument in a function name in the cart-update functions: their implementations are very similar, and the field named in each function name determines the difference in its implementation.
Why it matters: Spotting the difference hidden in function names helps explain why these functions are so repetitive. - Meaning of a code smell: Describe what the section means by a code smell and why noticing one calls for investigation rather than an immediate verdict that the code is bad.
Why it matters: The term helps you treat duplication as a clue to investigate, not an automatic judgment.
Key terms: implicit argument in function name, field name, duplication, code smell
10.4 Refactoring: Express implicit argument
This section shows how to turn a field hidden in a function name into an explicit argument, making the field name a first-class value and simplifying the API.
- Expressing an implicit argument: Describe the steps of the express implicit argument refactoring and apply them to the section’s setPriceByName() example.
Why it matters: It lets one function handle choices that previously required separate functions. - First-class field names: Explain what it means for the field name to become a first-class value in this example.
Why it matters: Treating the field name as a value lets ordinary language features work with it. - Smaller field-setting API: Explain how the generic function changes the API for clients and what clients still need to know.
Why it matters: Clients can set new fields without requesting a separate function for each one.
Key terms: express implicit argument, implicit argument, explicit argument, setPriceByName(), setFieldByName(), first-class value, field name, API specification, abstraction barrier, generic setFieldByName()
10.5 Recognize what is and what isn’t first-class
Defines first-class values in JavaScript, contrasts them with language constructs that are not values, and shows how a string argument made part of a function name first-class.
- First-class values and limits: Explain what makes a value first-class in JavaScript and distinguish such values from operators and control-flow keywords.
Why it matters: Recognizing what can be treated as a value helps you spot opportunities to make code more flexible. - String arguments for name fragments: Describe how the earlier refactoring made an implicit part of a function name first-class by replacing it with a string argument.
Why it matters: Turning an otherwise fixed piece of code into an argument gives the program more flexibility.
Key terms: first-class, first-class value, non-first-class, string argument, fields on objects
10.6 Will field names as strings lead to more bugs?
This section explains how to catch typos when field names are passed as strings.
- Validation of string field names: Distinguish compile-time and run-time checks for field names passed as strings, including when each check runs and how it can catch an invalid name.
Why it matters: Using strings for field names need not leave typos unchecked.
Key terms: field names, compile-time checks, run-time checks, static type system, TypeScript
10.7 Will first-class fields make the API hard to change?
This section explains how first-class field names can preserve an API’s abstraction barrier, then practices making field names explicit and checking which fields an API accepts.
- Stable field names and internals: Explain why accepting field names as strings does not require the API to keep the same internal field names.
Why it matters: It lets callers rely on a stable API without freezing the representation behind its abstraction barrier. - Explicit field argument refactoring: Describe how to remove duplication when the field being changed is encoded in a function name.
Why it matters: One function can handle the field-specific behavior that was duplicated across named functions. - Allowed field runtime checks: Describe the run-time check proposed for an increment function that accepts a field name.
Why it matters: An explicit field argument need not allow callers to increment every field.
Key terms: first-class field name, abstraction barrier, API spec, implicit argument in function name, express implicit argument, explicit argument, run-time checks, field name, 'size', 'quantity'
10.8 We will use a lot of objects and arrays
The section explains why the book represents general entities with generic data structures, while acknowledging the tradeoffs of interpreting data and the limits of type checking.
- General entities as data: Explain why the book represents shopping carts and items with JavaScript objects and arrays rather than requiring a custom interface for each entity.
Why it matters: This explains why the book’s carts and items often appear as ordinary objects and arrays. - Typing debate and language choice: Distinguish static from dynamic typing as defined here and explain why the book’s use of JavaScript does not settle or endorse either side of the debate.
Why it matters: You need not adopt the book’s example language or a position in the typing debate to use its ideas. - Runtime interpretation at boundaries: Explain why string-based field names pose real error risks but why static types cannot eliminate the need to check data arriving across a system boundary.
Why it matters: It separates the risks of interpreting incoming data from what a language’s type system can guarantee inside the program.
Key terms: data orientation, generic data structures, bespoke interfaces, objects, arrays, statically typed languages, dynamically typed languages, JavaScript, strings, JSON
10.9 First-class functions can replace any syntax
The section shows how functions can turn non-first-class syntax into values and explains how first-class functions make higher-order functions possible.
- First-class arithmetic operators: Explain how wrapping an arithmetic operator in a function makes an equivalent first-class value, even though the operator itself cannot be assigned to a variable.
Why it matters: Wrapping syntax in a function lets code use it where a value is needed. - Higher-order function relationship: Distinguish first-class functions from higher-order functions and explain why the former make the latter possible. Describe, at a conceptual level, how passing a function to another function relates to the proposed way to spare marketing from writing for loops.
Why it matters: The distinction explains why passing behavior to a helper can spare its callers from writing troublesome syntax.
Key terms: first-class values, operator, function, first-class, higher-order function, argument, callback
10.10 For loop example: Eating and cleaning up
The section refactors two similar array loops into a reusable forEach() function by passing their differing behavior as a function argument.
- Shared array-loop refactoring: Trace how the eating and dishwashing loops become identical by wrapping them in functions, using more generic names, expressing the array as an argument, and extracting and passing the differing loop body as a function.
Why it matters: This shows how to find a reusable pattern in code that initially appears to do different jobs. - Higher-order forEach abstraction: Explain why the resulting forEach() is a higher-order function and how passing it a function avoids rewriting the array loop for each kind of work.
Why it matters: Passing behavior as an argument lets one loop serve many purposes without duplicating its iteration code.
Key terms: implicit argument in function name, express implicit argument, extract function, forEach(), higher-order function, function as an argument
10.11 Refactoring: Replace body with callback
This section shows how to remove repeated try/catch code by extracting the shared structure into a function and passing the varying body as a callback.
- Replace body with callback: Describe how to identify the unchanged before and after code around a varying body, extract the whole structure into a function, and pass the body as a function argument.
Why it matters: It lets you remove repeated surrounding code without losing the code that differs from one use to another. - Callback argument meaning: Explain what a callback means in this section and what the function receiving it is expected to do.
Why it matters: It clarifies how the extracted wrapper can run the different body supplied by each caller.
Key terms: replace body with callback, before-body-after, withLogging(), callback, handler functions
10.12 What is this syntax?
The section explains three ways to define a function and clarifies why a function defined where it is passed is called inline and anonymous.
- Global and local function definitions: Distinguish globally defined functions from locally defined functions by where they are named and where their names can be used.
Why it matters: Where a function is defined affects where its name is available. - Inline anonymous function definitions: Explain what it means to define a function inline and anonymously, and describe when the section says that form is useful.
Why it matters: This lets you recognize a function written directly where another function uses it.
Key terms: globally defined, locally defined, local scope, inline function, anonymous function
10.13 Why are we wrapping the code in a function?
This section explains why wrapping code in a first-class function lets a higher-order function run it later inside a reusable context.
- Deferred execution with functions: Explain how wrapping saveUserData(user) in a function defers its execution and lets the wrapped code be passed to another function.
Why it matters: Deferring code lets another function decide when and where to run it. - Execution inside a reusable context: Explain why withLogging() receives a function rather than the result of calling saveUserData(user), and how that lets it run the code inside its try/catch.
Why it matters: The timing of a call determines whether the intended error-handling context can surround it. - Callbacks and shared code: Describe how passing callbacks lets a higher-order function reuse shared code while varying the code executed within it.
Why it matters: A callback lets the team share one implementation of a context without limiting it to one operation.
Key terms: first-class, defer the execution, function, withLogging(), try/catch, callback, higher-order functions, context, duplication
10.14 Conclusion
The conclusion recaps the chapter’s introduction to first-class values, first-class functions, and higher-order functions, then points ahead to further exploration.
10.15 Summary
10.16 Up next…
This section previews further uses of higher-order functions and the two refactorings in the next chapter.
Where this is used later
- One code smell and two refactorings (chapter 12) · The earlier introduction of the implicit-argument code smell
- Making the field name explicit (chapter 14) · Applies the idea to making a field name explicit.
- One code smell and two refactorings (chapter 11) · the earlier introduction of the implicit-argument code smell
- Higher-order functions for values in objects (chapter 14) · Working with values in objects relies on treating entities as general-purpose data.
- A summary of our use of higher-order functions (chapter 14) · The earlier introduction to first-class functions.
- Implicit versus explicit model of time (chapter 17) · using first-class functions to represent behavior
- Returning functions from functions (chapter 11) · Returning functions illustrates the other form of higher-order function named here.
- Deriving map() from examples (chapter 12) · Supports deriving other functional iteration tools from repeated array-loop patterns.
Concepts explained in this chapter
- higher-order functionA function that accepts functions as arguments, returns functions as results, or otherwise treats functions as values.
- replace body with callbackA refactoring technique that extracts unchanged before-and-after code into a function and supplies the varying body as a callback argument.
- extract function argument refactoringA refactoring technique that converts information encoded implicitly in a function name or surrounding code into an explicit function argument.
- implicit argumentA code smell in which a function's name encodes a value that would more appropriately be supplied explicitly as an argument.
- lambdaA function expression that has no name and can be defined inline where it is used.
- static typingA type system that checks and constrains the types of program expressions before execution.
- loopAn iteration construct that repeatedly executes a block while progressing through a sequence or range of values.
- arrayAn indexed JavaScript collection that stores an ordered sequence of values and can change size.
- code smellA code smell is a characteristic of source code that may indicate deeper design or maintenance problems.
- refactoringThe systematic restructuring of existing code without changing its externally observable behavior.
- explicit inputAn input to a function that is supplied directly at the call site through an argument or parameter.
- inline functionA function defined at the place in a program where it is used rather than assigned to a separate name.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
