liveBook · Manning

Invariant

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

An invariant is a condition that must remain true for a value or domain entity to be considered valid.

An invariant is a rule that must stay true for a domain value to be valid. Instead of repeatedly checking that rule with conditionals, represent valid and unvalidated states as different types. The compiler then prevents code that requires a valid value from receiving an unchecked one. section 8.3.5 section 14.1.1

A single-case discriminated union can wrap a primitive value and give it domain meaning. For example, PhoneNumber and CountryCode can both wrap strings while remaining incompatible types, so a phone number cannot accidentally be supplied where a country code belongs. This removes reliance on comments and catches mix-ups during compilation. section 8.3.5

Validated email as a boundary

Treat an entered email as unvalidated until a validation function constructs a ValidatedEmail. Make email sending accept only ValidatedEmail. Validation is therefore required before sending, while the sending function can stay on its happy path without rechecking the address. section 8.3.5 section 14.1.1

type ValidatedEmail =
    private | ValidatedEmail of string                             #1
    member this.Value = match this with (ValidatedEmail e) -> e    #2
    static member Create (unvalidatedEmail:string) =               #3
        if unvalidatedEmail.Contains "@" then
            Ok (ValidatedEmail unvalidatedEmail)
        else
            Error "Invalid email!"
 
let sendEmailSafe message (email:ValidatedEmail) =                 #4
    ...
    ...
    ...
    Ok "Sent message"
 
let sendResult =
    ValidatedEmail.Create "isaac@email.com"
    |> Result.bind (sendEmailSafe "Welcome!")                      #5

A private union case restricts construction of ValidatedEmail to its Create member. Creation returns either a validated value or an error, and Result.bind passes only a successful value to sendEmailSafe. section 14.1.1

Use invariants most effectively by:

  • Separating raw input from a value that has passed validation. section 8.3.5
  • Making operations accept the validated type rather than checking flags internally. section 14.1.1
  • Testing validation at the construction point instead of duplicating invalid-input tests across downstream operations. section 14.1.1

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

How deep it goes
Explains typed validation with single-case discriminated unions and private construction, without detailing validation beyond an @ check.
Examples
Uses phone numbers and country codes as distinct wrappers, then models unvalidated and validated email addresses.
What it assumes
Introduces F# domain types and validation examples without assuming prior F# or .NET knowledge.
What you can do after
Apply it via single-case discriminated unions and private constructors to prevent invalid values reaching downstream functions.
How it connects
Connects discriminated unions, type systems, compiler checking, Result.bind, and automated testing.

Also explained in

Compare how 12 books explain invariant →

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.