A compiler translates source code while checking it against language rules. In F#, it is most useful as an active partner: type errors and incomplete cases guide changes while a model and its functions are still taking shape. This shifts work toward correcting code before running the application, rather than discovering as many problems through end-to-end trial and runtime debugging. section 1.5.3 section 2.1.4
Treat compiler feedback as a design constraint, not an obstacle. When a record gains a field, every initialization must explicitly supply it. Optional values require missing data to be handled rather than replaced with null. These checks make changes visible across a system and force decisions at the relevant assignments and branches. section 2.1.4
Guiding unexpected type inference
Suppose calculateGroup compares age with 18. Changing 18 to "test" causes the compiler to infer that age is a string, so later numeric comparisons and integer call sites fail too. Inspect the first error and the inferred function type; then add a temporary int annotation to age. The error moves to the incorrect comparison, where the fix belongs. section 3.2.4
Listing 2.1 A domain model of a train system
open System
type Feature = Quiet | Wifi | Toilet #1
type CarriageClass = First | Second #2
Type CarriageKind = #3
| Passenger of CarriageClass
| Buffet of {| ColdFood : bool; HotFood : bool |}
type CarriageNumber = CarriageNumber of int #4
type Carriage = #5
{
Number : CarriageNumber
Kind : CarriageKind
Features : Feature Set
NumberOfSeats : int
}
type TrainId = TrainId of string
type Station = Station of string
type Stop = Station * TimeOnly #6
type Train = #7
{
Id : TrainId
Carriages : Carriage list
Origin : Stop
Stops : Stop list
Destination : Stop
DriverChange : Station option
}
This train model makes the compiler’s role concrete: its unions, records, lists, sets, tuples, and optional DriverChange encode the shapes that later code must handle. section 2.1.4
Compiler checks can also reduce narrowly focused tests when types enforce prerequisites. A private ValidatedEmail type separates validation from sending: sendEmailSafe cannot be called with an unvalidated string. This does not mean compiled code needs no tests; validation and other behavior can still be tested where failures remain possible. Exhaustive pattern matching likewise lets the compiler flag unhandled discriminated-union cases. section 14.1.1








