Chapter 6: Staying immutable in a mutable language
Introduces the chapter’s plan to use copy-on-write for arrays, objects, and deeply nested data.
After this chapter you will be able to
- Nested shopping cart data. Explain what nested and deeply nested data mean, and why updating an item inside a shopping cart is an example of working with nested data.
- Read and write operations. Distinguish reads from writes in the shopping-cart and item examples, and explain why reads that get information from their arguments are calculations.
- Discipline for mutable writes. Explain why writes need an immutability discipline and why JavaScript programmers must apply copy-on-write explicitly.
- Copy-on-write steps and isolation. Describe how to use copy-on-write to produce an updated value and explain why the original remains unchanged.
- Cart removal by copy-on-write. Trace how remove_item_by_name() changes from modifying its cart argument to returning an updated cart without modifying the original.
- Reusable copy-on-write operations. Explain why the section generalizes copy-on-write operations and identify the splice operation used in remove_item_by_name() as its example.
- Array mutation and return values. Distinguish the array operations that mutate the array from .slice(), and describe what the listed add and remove operations return.
- Mailing list copy-on-write. Apply the section’s copy-on-write instructions to add a contact without having add_contact() access or mutate the global mailing list directly.
- Read and write operations. Explain why .shift() is both a read and a write, and why that creates a question for copy-on-write.
- Two approaches and preference. State the two approaches named for an operation that both reads and writes, and explain why splitting is preferred when there is a choice.
- Separating shift reads and writes. Explain how to split .shift() into a read that returns the first element and a write that drops it, then convert the write to copy-on-write. Describe why separating them is useful.
- Two-result copy-on-write conversion. Describe how to wrap an operation such as .shift() or .pop(), make a copy, modify the copy, and return both the removed element and the modified copy without changing the original array.
- Immutable reads become calculations. Trace how eliminating writes makes data immutable and changes the classification of its reads.
- Mutable reference to immutable carts. Explain how the application tracks cart changes over time: it creates a new cart value and assigns it to the mutable shopping_cart global variable, which always points to the current cart.
- Immutability and performance tradeoffs. Explain why the section recommends starting with immutable data even though it generally costs more memory and time than mutable data.
- Shallow copies and shared structure. Describe what copy-on-write copies in the shopping-cart example and how structural sharing limits the work.
- Object copy-on-write steps. Describe how to set an item object's price without changing the original: copy the object, modify the copy, and return it. Explain that Object.assign() can make the copy by copying keys and values into an empty object.
- Object structure and lookup. Describe JavaScript objects as collections of unique string keys and values of any type, and explain how bracket and dot lookup work.
- Object mutation and copying. Explain which object operations mutate an object and how Object.assign() can create a copy for a copy-on-write update.
- Nested copy-on-write pattern. Explain how to update an item's price by copying the item and the cart rather than modifying either original value. Describe how setPrice(), which operates on items, is used inside setPriceByName(), which operates on carts.
- Immutability throughout nested data. Explain why leaving the cart array unchanged is not enough if an item inside it is modified directly.
- Copies and structural sharing. Explain which parts of the shopping cart are copied when setPriceByName() changes the t-shirt’s price, and use shallow copy and structural sharing to describe why the other items need not be copied.
- Copies along the change path. Trace the t-shirt price change: a shallow copy of the cart array is made, setPrice() copies the t-shirt object and changes its price, and the new object replaces the old one in the copied array. Apply the same rule to the socks example: copy the changed item and the array containing it.
- Safe sharing of unchanged items. Explain why the original and copied shopping carts can point to the same unchanged item objects, and state the condition that makes this sharing safe.
- Consistent copy-on-write in JavaScript. Explain why copy-on-write requires deliberate effort in JavaScript and how wrapper functions help maintain the discipline.
What it builds on
- Extracting a copy-on-write pattern (chapter 5) · Previously introduced a copy-on-write pattern.
- Actions, calculations, and data (chapter 3) · The distinction between actions, calculations, and data.
- Principle: Design is about pulling things apart (chapter 5) · The design principle of pulling things apart.
Section by section
Introduction
Introduces the chapter’s plan to use copy-on-write for arrays, objects, and deeply nested data.
6.1 Can immutability be applied everywhere?
The section recalls copy-on-write and defines nested data to frame why updating an item inside a shopping cart is a harder immutability problem.
- Nested shopping cart data: Explain what nested and deeply nested data mean, and why updating an item inside a shopping cart is an example of working with nested data.
Why it matters: Recognizing nested data makes clear why changing an item in the cart needs special attention. - Copy-on-write discipline: Describe the copy-on-write discipline already used for some shopping cart operations.
Why it matters: This is the existing approach the section brings to the question of immutable cart operations.
Key terms: nested data, deeply nested, copy-on-write discipline
6.2 Categorizing operations into reads, writes, or both
The section classifies shopping-cart and item operations as reads or writes and explains why writes need an explicit immutability discipline in JavaScript.
- Read and write operations: Distinguish reads from writes in the shopping-cart and item examples, and explain why reads that get information from their arguments are calculations.
Why it matters: Classifying an operation tells you whether it needs attention to preserve immutability. - Discipline for mutable writes: Explain why writes need an immutability discipline and why JavaScript programmers must apply copy-on-write explicitly.
Why it matters: It explains why an ordinary modifying operation is not enough when values may be shared.
Key terms: reads, writes, calculations, immutable discipline, copy-on-write, mutable data
6.3 The three steps of the copy-on-write discipline
Explains the three-step copy-on-write discipline and why it lets a function produce an updated value without changing the original data.
- Copy-on-write steps and isolation: Describe how to use copy-on-write to produce an updated value and explain why the original remains unchanged.
Why it matters: This lets you update a value in a mutable language without changing the original that other code may use. - Write converted into read: Explain why add_element_last() counts as a read after using copy-on-write, even though it produces an array with an added element.
Why it matters: The distinction helps you recognize that producing an updated value is different from changing existing data.
Key terms: copy-on-write discipline, copy, local scope, immutable, read, write, add_element_last(), copy-on-write
6.4 Converting a write to a read with copy-on-write
The section converts a cart-removal function to copy-on-write and explains how callers must use its returned cart.
- Cart removal by copy-on-write: Trace how remove_item_by_name() changes from modifying its cart argument to returning an updated cart without modifying the original.
Why it matters: This lets the removal function produce an updated cart without changing the cart it was passed. - Caller assignment of returned cart: Explain why code calling the converted remove_item_by_name() must assign its return value to shopping_cart.
Why it matters: Returning an updated cart changes how callers must keep the application's cart up to date.
Key terms: copy-on-write, remove_item_by_name(), cart.splice(), call site, return value, global shopping_cart
6.5 Complete diff from mutating to copy-on-write
6.6 These copy-on-write operations are generalizable
This section explains why recurring copy-on-write operations should be made reusable, using array splice in remove_item_by_name() as an example.
- Reusable copy-on-write operations: Explain why the section generalizes copy-on-write operations and identify the splice operation used in remove_item_by_name() as its example.
Why it matters: A reusable operation saves effort when the same copy-on-write pattern appears throughout the code.
Key terms: copy-on-write, splice(), remove_item_by_name(), boilerplate
6.7 JavaScript arrays at a glance
This section surveys JavaScript array operations and applies them to a copy-on-write mailing-list update.
- JavaScript array characteristics: Describe JavaScript arrays as ordered, index-accessible collections that can contain different types of values and can grow or shrink.
Why it matters: These basics make the array operations in the section easier to interpret. - Array mutation and return values: Distinguish the array operations that mutate the array from .slice(), and describe what the listed add and remove operations return.
Why it matters: Copy-on-write depends on knowing which operations change an existing array and which produce a copy. - Mailing list copy-on-write: Apply the section’s copy-on-write instructions to add a contact without having add_contact() access or mutate the global mailing list directly.
Why it matters: The example shows how an application can update its state while keeping the array-changing operation off the original input.
Key terms: array, index, .length, heterogeneous, assignment operator, .push(el), .pop(), .unshift(el), .shift(), .slice()
6.8 What to do if an operation is a read and a write
The section identifies operations that both read and write, then names two ways to handle them with copy-on-write.
- Read and write operations: Explain why .shift() is both a read and a write, and why that creates a question for copy-on-write.
Why it matters: Recognizing both roles prevents the returned element from being overlooked when changing the write. - Two approaches and preference: State the two approaches named for an operation that both reads and writes, and explain why splitting is preferred when there is a choice.
Why it matters: The preference gives a design reason for choosing between the two approaches.
Key terms: .shift(), read, write, copy-on-write, split the function, return two values, separates the responsibilities
6.9 Splitting a function that does a read and write
This section shows how to turn an operation that both reads and writes an array into separate operations, then make the write copy-on-write.
- Separating shift reads and writes: Explain how to split .shift() into a read that returns the first element and a write that drops it, then convert the write to copy-on-write. Describe why separating them is useful.
Why it matters: Separating the behaviors lets you take the first element, drop it, or do both without forcing every caller to use a combined operation.
Key terms: read, write, calculation, copy-on-write, first_element(), drop_first()
6.10 Returning two values from one function
Shows how to turn array operations that both return a value and mutate an array into copy-on-write reads, then discusses reusing those operations.
- Two-result copy-on-write conversion: Describe how to wrap an operation such as .shift() or .pop(), make a copy, modify the copy, and return both the removed element and the modified copy without changing the original array.
Why it matters: You can preserve both useful results of an array operation without mutating its input. - Separate read and write operations: Distinguish returning two results from the alternative of splitting a read-and-write operation into separate read and write functions, as shown for .pop().
Why it matters: Separating the two jobs offers another way to keep the useful read while eliminating mutation of the input. - Immutable reads and reusable helpers: Explain why a copy-on-write function counts as a read and why the section recommends writing reusable copy-on-write operations when needed.
Why it matters: Thinking of copy-on-write as answering a hypothetical question helps explain both its classification as a read and the value of reusable helpers.
Key terms: copy-on-write, read, write, .shift(), .pop(), calculations, add_element_to_cart()
6.11 Reads to immutable data structures are calculations
The section explains why converting writes to reads makes data immutable and turns reads of that data into calculations.
- Mutable reads and writes: Explain why reading mutable data is an action and how writes make data mutable.
Why it matters: This distinction explains why an ordinary read is not always a calculation. - Immutable reads become calculations: Trace how eliminating writes makes data immutable and changes the classification of its reads.
Why it matters: It connects the chapter’s write-to-read conversions to the goal of having more calculations and fewer actions.
Key terms: mutable data, actions, writes, immutable, reads, calculations
6.12 Applications have state that changes over time
This section explains how an application can track changing state while keeping each version of its shopping cart immutable.
- Mutable reference to immutable carts: Explain how the application tracks cart changes over time: it creates a new cart value and assigns it to the mutable shopping_cart global variable, which always points to the current cart.
Why it matters: This distinction lets you keep cart values immutable while still recording what the current cart is.
Key terms: shopping_cart global variable, swapping pattern, immutable
6.13 Immutable data structures are fast enough
Explains why immutable data is usually fast enough in practice despite its performance costs.
- Immutability and performance tradeoffs: Explain why the section recommends starting with immutable data even though it generally costs more memory and time than mutable data.
Why it matters: This gives you a practical default without ruling out performance optimizations. - Shallow copies and shared structure: Describe what copy-on-write copies in the shopping-cart example and how structural sharing limits the work.
Why it matters: Copying references rather than every item helps explain why copy-on-write can be practical. - Garbage collection performance: Explain why the section treats garbage collection as one reason immutable data can be practical, while retaining its qualification.
Why it matters: Garbage-collector performance is useful evidence, but it is not a guarantee for every language. - Built-in immutable data structures: Describe how the section says built-in immutable structures can outperform the straightforward JavaScript routines used so far.
Why it matters: The simple JavaScript implementation is not the performance ceiling for immutable data.
Key terms: immutable data, mutable data, performance bottlenecks, copy-on-write, shallow copy, structural sharing, references, garbage collector, immutable data structures
6.14 Copy-on-write operations on objects
This section applies copy-on-write to JavaScript objects and explains how shallow copies create structural sharing.
- Object copy-on-write steps: Describe how to set an item object's price without changing the original: copy the object, modify the copy, and return it. Explain that Object.assign() can make the copy by copying keys and values into an empty object.
Why it matters: This lets you update an item object while leaving the original unchanged. - Shallow copies and shared references: Explain what a shallow copy duplicates in nested data and why the original and copy can share references. State why the section says structural sharing is useful when the data is immutable.
Why it matters: It explains why copying a collection need not duplicate every object inside it.
Key terms: copy-on-write, Object.assign(), set_price(), shallow copies, structural sharing, references
6.15 JavaScript objects at a glance
This section summarizes JavaScript object operations and shows how copying an object supports copy-on-write updates.
- Object structure and lookup: Describe JavaScript objects as collections of unique string keys and values of any type, and explain how bracket and dot lookup work.
Why it matters: Knowing how object keys and lookups behave makes the later update examples easier to follow. - Object mutation and copying: Explain which object operations mutate an object and how Object.assign() can create a copy for a copy-on-write update.
Why it matters: Copying before a write lets you update an object without changing the original. - Object key enumeration: Explain how Object.keys() enables iteration over an object’s key/value pairs.
Why it matters: A list of keys gives you a way to process an object’s entries one by one.
Key terms: object, key/value pairs, undefined, dot notation, delete, Object.assign(), copy-on-write, objectSet(), Object.keys()
6.16 Converting nested writes to reads
This section shows how to convert a nested shopping-cart write into a read while keeping the original cart and its items unchanged.
- Nested copy-on-write pattern: Explain how to update an item's price by copying the item and the cart rather than modifying either original value. Describe how setPrice(), which operates on items, is used inside setPriceByName(), which operates on carts.
Why it matters: The same copy-on-write discipline works when the value being updated is inside another data structure. - Immutability throughout nested data: Explain why leaving the cart array unchanged is not enough if an item inside it is modified directly.
Why it matters: An unchanged outer array does not protect the original data if an object inside it changes.
Key terms: nested writes, copy-on-write, setPrice(), setPriceByName(), immutable, nested data structure, references
6.17 What gets copied?
The section uses a shopping-cart price change to show that copying nested data can preserve unchanged objects through structural sharing.
- Copies and structural sharing: Explain which parts of the shopping cart are copied when setPriceByName() changes the t-shirt’s price, and use shallow copy and structural sharing to describe why the other items need not be copied.
Why it matters: It shows why updating nested data does not require copying every object in it.
Key terms: nested data, shallow copy, structural sharing, setPriceByName()
6.18 Visualizing shallow copies and structural sharing
The section uses a shopping-cart price change to show which pieces copy-on-write duplicates and which unchanged pieces the old and new carts can safely share.
- Copies along the change path: Trace the t-shirt price change: a shallow copy of the cart array is made, setPrice() copies the t-shirt object and changes its price, and the new object replaces the old one in the copied array. Apply the same rule to the socks example: copy the changed item and the array containing it.
Why it matters: This lets you work out what a nested copy-on-write change actually needs to copy. - Safe sharing of unchanged items: Explain why the original and copied shopping carts can point to the same unchanged item objects, and state the condition that makes this sharing safe.
Why it matters: It explains why keeping an original value does not require copying every object it contains.
Key terms: copy-on-write, shallow copy, setPrice(), setPriceByName(), structural sharing
6.19 Conclusion
The conclusion explains why copy-on-write in JavaScript takes discipline and why utility functions make it easier to follow consistently.
- Consistent copy-on-write in JavaScript: Explain why copy-on-write requires deliberate effort in JavaScript and how wrapper functions help maintain the discipline.
Why it matters: Consistently using the wrappers makes copy-on-write easier to sustain in JavaScript.
Key terms: copy-on-write discipline, utility functions, wrapper functions
6.20 Summary
6.21 Up next…
The section previews defensive copying as a way to exchange data with existing code that does not follow copy-on-write.
Where this is used later
- Refactoring copy-on-write for arrays (chapter 11) · Refactoring copy-on-write for arrays builds on the value of making repeated array operations reusable.
- Mutable state in functional programming (chapter 18) · Provides an example of mutable state used alongside immutable values.
- Using update() to modify values (chapter 14) · Explains how applications use immutable values while state changes over time.
- Deriving update() (chapter 14) · Copy-on-write operations on objects.
- Visualizing nested updates (chapter 14) · Visualizing nested updates builds on identifying the changed value and the containers above it.
- Deep copies are more expensive than shallow copies (chapter 7) · Shallow copies and structural sharing
- Defensive copying defends the immutable original (chapter 7) · Defending an immutable original depends on recognizing that shared data must not be modified.
- Refactoring copy-on-write (chapter 11) · Refactoring copy-on-write builds on the motivation for wrapping repeated copy-on-write work.
Concepts explained in this chapter
- copy-on-writeA data-management technique that creates a copy before modifying data so the original remains unchanged.
- shallow copyA shallow copy is a copy of a data structure's top-level container that retains references to the nested values rather than copying them recursively.
- structural sharingStructural sharing is a data-structure technique in which multiple versions reuse references to common portions of their representation.
- read operationA read operation obtains the current value of data without modifying that data.
- write operationA write operation modifies the stored value or state of data.
- immutable collectionData or state whose represented values cannot be modified after creation.
- arrayAn indexed JavaScript collection that stores an ordered sequence of values and can change size.
- cart operationA computation that operates on a shopping cart while depending on the cart's structural representation.
- defensive copyA technique that copies data intended to be retained so subsequent modifications cannot alter the retained version.
- item operationA computation that operates on an item while depending on the item's structural representation.
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.
