Refining unions across function boundaries
Carry safe narrowing across function boundaries using discriminated unions, predicate returns, and Result-style types.
Intro
Goal: Keep your narrowing intact across function calls using tagged unions, predicate returns, and validation helpers.
- Zero unsafe
asat call sites - Refinements travel with data
Tagged union return
Result pattern: caller narrows by checking ok. No casts, clear control flow.
type Ok<T> = { ok: true; value: T }
type Err = { ok: false; error: string }
type Result<T> = Ok<T> | Err
function parseIntSafe(s: string): Result<number> {
const n = Number(s)
return Number.isFinite(n) ? { ok: true, value: n } : { ok: false, error: "NaN" }
}
function useIt(s: string) {
const r = parseIntSafe(s)
if (r.ok) {
// r is Ok<number>
return r.value * 2
}
// r is Err
return `bad: ${r.error}`
}All lessons in this course
- Exhaustive switches & never checks
- Predicate functions & satisfies operator
- Refining unions across function boundaries