Skip to content

Nothing is null: ?T

Use ?T for everyday absence in Hem-owned application models, and None to construct an absent value. Handle it with matching, defaulting, or a checked binding. You do not need to choose a JavaScript sentinel for each new application field.

?T means T | null | undefined, unboxed. It gives both values the same absence-handling operations; it does not convert existing values to one runtime representation.

Hem provides five ways to eliminate a ?T value:

match cache.get(key) {                 // 1. match — both cases, checked
| Some(hit) => hit
| None => recompute(key)
}

let title = doc?.head?.title ?? "untitled"   // 2 & 3. optional chaining, defaulting

let Some(user) = findUser(id) else return notFound()   // 4. let-else guard clause

if el != None { el.focus() }           // 5. comparison — tsc narrows `el` to HTMLElement

Optional calls are also supported: onProgress?.(update) emits an explicit nullish check rather than a truthiness check, yielding R | undefined for an R-returning callback.

Some and None are type-directed. As patterns on nullish values they lower to nullish checks; as expressions in a nullish context, Some(e) is just e and None emits null or undefined by the target type:

node.textContent = None                // → null — the DOM setter takes string | null
let found: ?Element = None             // → undefined

Comparisons are the one place Hem emits a loose check on purpose: x != None compiles to x != null, the TypeScript idiom that eliminates null and undefined together in a single evaluation, which is exactly what “not None” means.

The let-else refutable pattern set also heads a loop: while let Some(line) = input.next() { … } re-tests it each iteration and stops when it fails (§10).

Use ?T for ordinary unboxed absence. Choose boxed Option<T> when you need the distinct states described below; preserve exact types when an external contract requires them. Bare Some(x) refuses with HEM2025; bare None infers undefined. On canonical Option, qualified Option.some(value) and Option.none make the boxed representation explicit. Lexically bound constructor names keep their ordinary meaning.

Because ?T is unboxed, generic instantiation collapses; Map<string, ?number>.get cannot distinguish an absent key from an entry whose value is undefined. In a nullish context Some(e) does not establish presence: a nullish payload remains absent. Use boxed Option<Option<T>> when outer absence, a present inner absence, and a present value must remain distinct.

Preserve an external contract

Foreign APIs can distinguish the two JavaScript absence values: document.getElementById returns Element | null, while map.get(k) returns V | undefined. Hem retains those types. When implementing such a contract, write the exact union or reuse the library’s exported type. Replacing either with ?T widens the public API. Exact unions are available throughout Hem; using them for interop is authoring guidance, not a compiler restriction.

None constructs absence in the expected type. Changing an annotation from T | null to ?T can therefore change its emitted value from null to undefined. Assigning or propagating an existing value preserves its sentinel. See the construction and refactoring example before changing an externally visible contract.

The rest of the rules live in the absence reference: exactly how None picks its emit, the evaluation order of an optional call, the refused ??T, and the exact boundary contract rule.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close