Just for clarity: what you've described are (Units of Measure)[https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...], which let you define types, and their relationships, so you can define meters, and kilometers as meters * 1000 and then rest safe that your spaceshuttle will be using the right units.
Unit of Measure in practice:
[<Measure>] type cm
[<Measure>] type ml = cm^3 // a millilitre is 1 cm cubed
To the point in the article about using types to convey meaning, as I commented elsewhere, you want F#s awesome type aliasing and single-case discriminated unions :)
Type aliasing:
type CustomerId = int // We can send now use IDs interchangeably with int, use CustomerId in method signatures, and expand the type with methods
Single Case Discriminated Unions:
type CustomerId = CustomerId of int
let id = CustomerId (3) // Have to use a specific constructor
let myFunction (CustomerId(id)) = // DUs can be unpacked in parameters
printfn "%i" id // this function only accepts CustomerId's
That's what it was called, sorry I was citing from the top of my head. I don't find the name Units of Measure a bit confusing as it seems to imply that it's only use is scientific units while there are plenty of use cases outside of that realm.
A SandwichCount should not be confused with a BurgerCount!
Unit of Measure in practice: [<Measure>] type cm [<Measure>] type ml = cm^3 // a millilitre is 1 cm cubed
To the point in the article about using types to convey meaning, as I commented elsewhere, you want F#s awesome type aliasing and single-case discriminated unions :)
Type aliasing:
Single Case Discriminated Unions: