These — Inclusive OR
TypeScript models exclusive-OR states with discriminated unions (A | B) and product states with
objects ({ a: A; b: B }). However, operations like data synchronization, diff engines, and
non-fatal warnings require modeling inclusive-OR states: having a first value, a second value, or
both simultaneously.
These<A, B> represents inclusive-OR as a discriminated union of three variants: First<A>,
Second<B>, and Both<A, B>: offering three distinct variants:
First(a)— only the first value is present.Second(b)— only the second value is present.Both(a, b)— both the first and second values are present.
flowchart TD
Start([Data Presence Check]) --> Choice{What is present?}
Choice -->|Only A| First[First A]
Choice -->|Only B| Second[Second B]
Choice -->|Both A and B| Both[Both A B]
These is a neutral, structural domain-modeling type. It does not carry any success, failure, or
implicit error connotations. Neither side is privileged.
Structural Modeling: Domain Examples
Section titled “Structural Modeling: Domain Examples”1. Contact Information
Section titled “1. Contact Information”Suppose you are designing a notification service that requires contact details. A user can register
their email address A, their phone number B, or both Both(A, B). Representing this with
standard union types or optional fields is fragile because it permits the invalid state of having
neither.
With These, the type system enforces that at least one contact channel is present:
2. Local and Remote Synchronization
Section titled “2. Local and Remote Synchronization”Suppose you are building a database synchronizer that reconciles local and remote changes. When comparing records, three outcomes are structurally possible:
- Only local changes exist:
First(local) - Only remote changes exist:
Second(remote) - Both local and remote changes exist and must be merged:
Both(local, remote)
Transforming Values
Section titled “Transforming Values”Because These carries two distinct type parameters, we can map over either or both sides
independently.
Mapping the first side with mapFirst
Section titled “Mapping the first side with mapFirst”mapFirst transforms the value inside a First or a Both container, leaving Second entirely
untouched:
Mapping the second side with mapSecond
Section titled “Mapping the second side with mapSecond”mapSecond transforms the value inside a Second or a Both container, leaving First untouched:
Mapping both sides with mapBoth
Section titled “Mapping both sides with mapBoth”mapBoth allows you to transform both paths simultaneously:
Chaining Transformations
Section titled “Chaining Transformations”Chaining operations over a These requires careful attention to what should happen to coexisting
values.
chainFirst passes the first value to the next step, leaving Second unchanged. When the input is
a Both variant, the coexisting second value is dropped, and the pipeline yields whatever the next
step returns:
If you need the second value to survive the chain, you must use pure maps rather than monadic chains.
chainSecond performs the symmetric operation, chaining over the second value while leaving First
unchanged:
Extracting Values
Section titled “Extracting Values”When you reach the boundary of your pipeline, you must unpack These into standard TypeScript
primitives.
Exhaustive matching with match and fold
Section titled “Exhaustive matching with match and fold”match requires you to handle all three possible variants explicitly, ensuring that coexisting
values are never accidentally lost:
For positional callbacks, fold provides an un-named positional mapping alternative.
Safe fallbacks with getFirstOrElse and getSecondOrElse
Section titled “Safe fallbacks with getFirstOrElse and getSecondOrElse”If you only want to extract one side of the container and provide a fallback if it is absent:
Direct Inspection: Type Guards
Section titled “Direct Inspection: Type Guards”To check which variant is active without unpacking the full structure, These provides several type
guards:
Utilities
Section titled “Utilities”Flipping roles: swap
Section titled “Flipping roles: swap”swap reverses the first and second values of the container:
Peeking with tap
Section titled “Peeking with tap”tap allows you to execute a side-effectful callback on the first value of a First or Both
container, leaving the original These unchanged:
Problems it solves
Section titled “Problems it solves”- Two-way offline and remote synchronization: When synchronizing records between local offline
stores (such as SQLite or IndexedDB) and a remote server, changes may exist exclusively locally
(new offline drafts), exclusively remotely (cloud updates), or concurrently on both sides (merge
conflicts).
Thesemodels inclusive three-way states (This,That,Both) without ambiguous nullable flags. - Partial success with non-fatal warnings: In batch processing and API transformations, an
operation often successfully parses a payload while also encountering non-fatal warnings or
recoverable format issues.
Theseallows functions to return both the valid payload and the collected warnings, avoiding the need to either fail the entire operation or discard diagnostic details. - Reconciling diff patches with custom strategies: When calculating database differences between
two snapshot versions,
These.foldandThese.matchhandle additions (This), deletions (That), and modifications (Both) with clean, case-specific logic. - Multi-channel communication delivery: In notification systems where alerts can be dispatched
via Email, SMS, or both channels simultaneously,
Thesemodels valid communication options and tracks per-channel delivery statuses cleanly.