Overview

3 Composition

This chapter explains how to build richer models by composing types in TypeScript. It contrasts grouping values together (compound/product types) with choosing among alternatives (either-or/sum types), and shows how these constructions help encode meaning and prevent misuse. Along the way it illustrates both built-in TypeScript features and small, hand-rolled generics, then applies the ideas to two takes on the Visitor pattern before tying everything together under the umbrella of algebraic data types (ADTs).

For product composition, the chapter starts with tuples as lightweight groupings and quickly advocates records for clarity: naming members removes ambiguity (x versus y) and makes call sites self-explanatory. It then focuses on invariants—rules that must always hold for well-formed values—demonstrating how public fields can be unsafe when members are interdependent (as with dollars and cents). Two enforcement strategies are presented: make fields private and funnel updates through validating methods, or make them readonly and push validation into constructors. The trade-offs (ease of use, safety, immutability, and concurrent access) are discussed, with the general guidance to prefer records and to guard invariants at the type boundary.

For sum composition, the chapter moves from enumerations (eliminating numeric ambiguity) to optional types (explicit absence via T | undefined or a generic Optional), and to Either for “result or error”—emphasizing returning a value or an error, not both, to eradicate invalid states. It generalizes to variants (tagged unions) for closed sets of alternatives and shows both union-based and generic Variant implementations plus a visit function that safely dispatches on the contained type. The Visitor pattern appears in two forms: the classical double-dispatch via interfaces and an alternative variant-based approach that decouples domain objects from visitors. The chapter concludes by framing these ideas as ADTs: product types (tuples/records) and sum types (optionals/either/variants), encouraging a “types as sets” mindset to minimize illegal states and make illegal states unrepresentable.

: Combining two types so the resulting type contains a value from each of them. Each emoji represents a value from one or the types. The parentheses represent the values of the combined type as pairs of values from the original types.
: Two ways to interpreting the pair (1, 5): as point A with X coordinate 1 and Y coordinate 5 or as point B with X coordinate 5 and Y coordinate 1
: Combining two types so the resulting type allows values from either of the two types
: All possible values of the Result type as combinations of InputError and DayOfWeek. That’s 21 values (3 InputError x 7 DayOfWeek).
: All possible values of Result type as a combination of InputError or DayOfWeek. That’s 9 values (2 InputError + 7 DayOfWeek). We no longer need an OK InputError as absence of an error is indicated by the fact that we have a DayOfWeek value.
A visitor pattern. IDocumentItem interface ensures every document item has an accept() method which takes an IVisitor. IVisitor ensures every visitor can handle all possible document item types. Each document item implements accept() to send itself to the visitor. With this pattern, we can separate responsibilities like screen rendering and accessibility to individual components (visitors) and abstract this away from the document items.
A simplified visitor pattern: now the document items and visitors don’t need to implement any interfaces. Contrast this with figure 3.6. The responsibility of matching a document item with the right visitor method is encapsulated in the visit() method. As we can see from the figure, none of the types are related at all, which is a good thing: it makes our program more flexible.

FAQ

What are compound (product) types and how do I create them in TypeScript?Compound types group values from multiple types together. In TypeScript you typically use: - Tuples for positional grouping, for example: type Point = [number, number] - Records for named fields, for example: type Point = { x: number; y: number } Both are product types because a value contains one value from each component type.
When should I use a tuple versus a record?Prefer records when names add meaning and prevent mix-ups (for example, { x, y } avoids confusing X and Y). Tuples are fine for small, obvious, ad‑hoc groupings or for inline returns, but lack field names and are easier to misinterpret.
How can I return multiple values from a function safely?Use a small record or a tuple to package outputs: - Tuple: function f(): [number, number] { ... } - Record: function f(): { width: number; height: number } { ... } Prefer a record if naming reduces ambiguity or improves readability.
How do I enforce invariants on records (like ensuring cents < 100 in a Currency type)?Either: - Make fields private and expose methods that validate and normalize values, or - Make fields readonly (immutable) and validate in the constructor. Both prevent external code from bypassing the rules. Example: validate that cents is non‑negative and normalize 100 cents into 1 dollar.
What is an optional type and why is it better than using null?An optional type represents “a value of T or no value.” In TypeScript: T | undefined (for example, DayOfWeek | undefined). It makes absence explicit in the type and forces you to check before use, avoiding null‑dereference errors and the ambiguity of “maybe null.”
How do I model “result or error” without exceptions?Return either an error or a valid value (not both). Options: - Discriminated union via a helper type like Either<E, T>, for example Either<InputError, DayOfWeek> - In languages with unions, use E | T plus a tag to discriminate. This eliminates invalid states like using a default value when there’s an error.
What is a variant (tagged union) and how do I use it in TypeScript?A variant holds a value of one of several types and carries a tag to tell which. In TS you can: - Use discriminated unions (add a kind field to each shape and switch on it), or - Implement a generic Variant<T1,T2,T3> that stores a value and an index, and use a helper visit() to apply the right function based on the tag.
How does the Visitor pattern work and what is double dispatch?The Visitor pattern separates operations from data. Each element implements accept(visitor) and the visitor implements methods like visitParagraph(), visitPicture(), etc. “Double dispatch” means the call is dispatched first on the element’s accept, then on the visitor’s specific visitX method, enabling new operations without changing element classes.
Can I implement Visitor-like behavior without class hierarchies?Yes. Store items in a variant and use a generic visit(variant, f1, f2, ...) to run the appropriate function. This decouples data classes from visitors (no interfaces or accept() needed) and encapsulates the type switching in one place.
What are Algebraic Data Types (ADTs), and how do product and sum types relate to tuples, records, and unions?ADTs are ways to combine types: - Product types (tuples, records) hold a value from each component type (A × B). - Sum types (unions, optionals, variants) hold a value from one of the component types (A + B). Use products to group related fields together, and sums to represent “either‑or” alternatives.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programming with Types ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programming with Types ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programming with Types ebook for free