liveBook · Manning

Exhaustiveness

As explained in F# in Action: A Revised Edition of Get Programming with F#

Exhaustiveness checking is compile-time verification that pattern matching handles every possible case of a type.

Exhaustiveness checking is compiler verification that a pattern match covers every case a type can produce. It is especially useful with discriminated unions: when every union case is written explicitly, the compiler can confirm that no case was missed. A match over a free-form string cannot offer the same guarantee, because any other string may arrive. section 8.3.3

The practical benefit appears when a domain changes. If a ContactMethod union gains an Sms case, every match that has not handled Sms produces a warning. Adding the missing branch removes the warning, turning a potentially scattered maintenance task into a compiler-guided update. section 8.3.3

Match every case explicitly

A union for instruments can be matched without a fallback: type Instrument = | Guitar | Drums | Bass let play instrument = match instrument with | Guitar -> "Wah wah" | Drums -> "Ba dam tss" | Bass -> "Ba ba bom" Every Instrument value produces a string. If another Instrument case is added later, this match is no longer complete and the compiler can identify it. section 14.1.1

Coverage also descends through nested unions. An Sms contact method can contain either a Local or International TelephoneNumber. Matching only Sms (Local number) leaves International uncovered, and the warning names that missing nested shape rather than merely reporting a vague failure. section 8.3.4

Incomplete pattern matches on this expression. For example, the value 'Sms
(_)' may indicate a case not covered by the pattern(s)

The diagnostic illustrates the feedback loop: adding a union case reveals a match that must be updated before its handling is complete. section 8.3.3

Use exhaustiveness as a design and maintenance aid:

  • Model bounded domain states with union cases rather than broad values such as integers, Booleans, or strings when the possible states are known. section 8.3.4
  • Prefer explicit branches when distinct cases may need different behavior later; a wildcard can silently absorb a newly added case. section 8.3.4 section 14.1.1
  • Read warnings, or treat warnings as errors, so incomplete matches are acted on during development. section 14.1.1
  • Still test the behavior chosen for each case when correctness depends on the result, such as associating the right instrument text with a case. section 14.1.1

A total match is not a claim that the chosen business result is correct; it only establishes that each representable case has a branch. Types and completeness checking can remove the need to test whether a union case was omitted, while tests remain useful for validation logic and for checking that each branch does the intended thing. section 8.3.4 section 14.1.1

How F# in Action: A Revised Edition of Get Programming with F# covers it

How deep it goes
Explains direct and nested union coverage, compiler diagnostics, wildcard tradeoffs, and testing limits.
Examples
Uses ContactMethod with SMS numbers, loan-decision domain types, and an Instrument-to-sound function.
What it assumes
Introduces completeness through discriminated unions and pattern matches without requiring prior F# or .NET knowledge.
What you can do after
Evaluate/understand whether union matches remain complete and when explicit cases are safer than wildcards.
How it connects
Connects discriminated unions, pattern matching, wildcards, domain modeling, compiler warnings, and testing.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of F# in Action: A Revised Edition of Get Programming with F#. The section links point to the parts of the book it draws on.