Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Preserve types with generics

Project overview and reading path

A helper selects the first job from a batch. Callers need its job type, and selection requires at least one item.

Typical Python

def first(items):
    return items[0]


job = first(jobs)
empty_job = first(())

The helper has no declared relationship between its input and output types. Callers rely on knowing what jobs contains. An empty collection also fails with IndexError when selected.

Alternative

T = TypeVar("T")


@dataclass(frozen=True)
class Batch(Generic[T]):
    head: T
    rest: tuple[T, ...] = ()


def first(batch: Batch[T]) -> T:
    return batch.head


job = first(Batch(Job("report")))

Source

first(Batch(Job("report"))) returns a Job. Assigning it to str is rejected; T connects the stored item type to the return type. Requiring head also makes Batch() a missing-argument error before execution.

The example selects Job(name="report"). An external collection can still be empty: its boundary must handle absence before constructing the batch. A required first item is useful only after that decision; an unchecked items[0] would merely move the runtime failure elsewhere.