पाठ 17 / 25

Pattern Matching and Exhaustiveness

Switch on tags and let the compiler check every case.

Handling every variant

Pattern matching inspects the shape of a value and runs the branch for the matching variant, often extracting fields at the same time. Languages such as Haskell, Rust and OCaml check that matches are exhaustive, so adding a new variant produces errors at every place that forgot to handle it. In TypeScript, switch on the discriminant narrows each branch; assigning the value to a variable of type never in the default branch turns a missing case into a compile error. Python 3.10+ provides structural pattern matching with match/case, but it is not checked for exhaustiveness at runtime; type checkers can help.

Exhaustive switch with never

Add a variant and the compiler points to this function.

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rect'; width: number; height: number };

function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${JSON.stringify(x)}`);
}

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle':
      return Math.PI * s.radius ** 2;      // s narrowed to circle
    case 'rect':
      return s.width * s.height;           // s narrowed to rect
    default:
      return assertNever(s);               // compile error if a case is missing
  }
}

Python match

With dataclasses Circle(radius) and Rect(width, height), Python 3.10+ can write match shape: then case Circle(radius=r): return math.pi * r ** 2 and case Rect(width=w, height=h): return w * h. Add a final case _: that raises, since Python does not enforce exhaustiveness.

त्वरित जाँच: What does the `never` assignment in the default branch achieve?

  • It catches network errors
  • It makes the switch run faster
  • It makes the compiler report an error if any variant is not handled
  • It converts the union to a class
Answer

It makes the compiler report an error if any variant is not handled — Only an impossible value is assignable to never.