βοΈ Recommendation:Strive for Consistency
β Apply consistently the same multi-line style across a repository
β In addition, use the single-line style when relevant: line with < 80 chars
// `with` keyword required
type PostalAddress = { Address: string; City: string; Zip: string } with
member x.ZipAndCity = $"{x.Zip} {x.City}"
// Or use line breaks (recommended when >= 2 members)
type PostalAddress =
{ Address: string; City: string; Zip: string }
member x.ZipAndCity = $"{x.Zip} {x.City}"
member x.CityAndZip = $"%s{x.City}, %s{x.Zip}"
Multi-line Cramped and Aligned styles
βοΈ 2 line breaks
type PostalAddress =
{ Address: string
City: string
Zip: string }
member x.ZipAndCity = $"{x.Zip} {x.City}"
member x.CityAndZip = $"%s{x.City}, %s{x.Zip}"
Multi-line Stroustrup style
βοΈ with keyword + 1 line break + indentation
type PostalAddress = {
Address: string
City: string
Zip: string
} with
member x.ZipAndCity = $"{x.Zip} {x.City}"
member x.CityAndZip = $"%s{x.City}, %s{x.Zip}"
Construction: record expression
Same syntax as an anonymous Cβ― object without the new keyword
All fields must be populated, but in any order (but can be confusing)
Same possible styles: single/multi-lines
type Point = { X: float; Y: float }
let point1 = { X = 1.0; Y = 2.0 }
let pointKo = { Y = 2.0 } // π₯ Error FS0764
// ~~~~~~~~~~~ FS0764: No assignment given for field 'X' of type 'Point'
β οΈ Trap: differences declaration / instanciation
β : for field type in record declaration
β = for field value in record expression
Deconstruction
Fields are accessible by "dotting" into the object
Alternative: deconstruction
Same syntax for deconstructing a Record as for creating it π
Unused fields can be ignored π‘
let { X = x1 } = point1
let { X = x2; Y = y2 } = point1
β οΈ Additional members (properties) cannot be deconstructed!
type PostalAddress =
{
Address: string
City: string
Zip: string
}
member x.CityLine = $"{x.Zip} {x.City}"
let address = { Address = ""; City = "Paris"; Zip = "75001" }
let { CityLine = cityLine } = address // π₯ Error FS0039
// ~~~~~~~~ The record label 'CityLine' is not defined
let cityLine = address.CityLine // π OK
Inference
A record type can be inferred from the fields used π but not with members β
As soon as the type is inferred, IntelliSense will work
type PostalAddress =
{ Address: string
City: string
Zip: string }
let department address =
address.Zip.Substring(0, 2) |> int
// ^^^^ π‘ Infer that address is of type `PostalAddress`.
let departmentKo zip =
zip.Substring(0, 2) |> int
// ~~~~~~~~~~~~~ Error FS0072: Lookup on object of indeterminate type
Pattern matching
Let's use an example: inhabitantOf is a function giving the inhabitants name (in French) at a given address (in France)
type Address = { Street: string; City: string; Zip: string }
let department { Zip = zip } = int zip[0..1] // Address -> int
let private IleDeFrance = Set [ 75; 77; 78; 91; 92; 93; 94; 95 ]
let inIleDeFrance departmentNum = IleDeFrance.Contains(departmentNum) // int -> bool
let inhabitantOf address = // Address -> string
match address with
| { Street = "Pôle"; City = "Nord" } -> "Père Noël"
| { City = "Paris" } -> "Parisien"
| _ when department address = 78 -> "Yvelinois"
| _ when department address |> inIleDeFrance -> "Francilien"
| _ -> "FranΓ§ais"
Name conflict
In Fβ―, typing is nominal, not structural as in TypeScript
β Use qualification to resolve ambiguity
β Even better: write β types or put them in β modules
type Person1 = { First: string; Last: string }
type Person2 = { First: string; Last: string }
let alice = { First = "Alice"; Last = "Jones"} // val alice: Person2... (by proximity)
// β οΈ Deconstruction
let { First = firstName } = alice // Warning FS0667 (in F# 6)
// ~~~~~~~~~~~~~~~~~~~~~ The labels of this record do not uniquely
// determine a corresponding record type
let { Person2.Last = lastName } = alice // π OK with qualification
let { Person1.Last = lastName } = alice // π₯ Error FS0001
// ~~~~~ Type 'Person1' expected, 'Person2' given
Modification: copy and update
Record is immutable, but easy to get a modified copy
β copy and update expression of a Record
β use multi-line formatting for long expressions
// Single-line
let address2 = { address with Street = "Rue Vivienne" }
// Multi-line
let address3 =
{ address with
City = "Lyon"
Zip = "69001" }
Copy and update: Cβ― vs Fβ― vs JS
// Record Cβ― 9.0
address with { Street = "Rue Vivienne" }
// Fβ― copy and update
{ address with Street = "Rue Vivienne" }
type Street = { Num: string; Label: string }
type Address = { Street: Street }
type Person = { Address: Address }
let person = { Address = { Street = { Num = "15"; Label = "rue Neuf" } } }
let person' =
{ person with
Address =
{ person.Address with
Street =
{ person.Address.Street with
Num = person.Address.Street.Num + " bis" } } }
Copy and update: Fβ― 8 improvements
type Street = { Num: string; Label: string }
type Address = { Street: Street }
type Person = { Address: Address }
let person = { Address = { Street = { Num = "15"; Label = "rue Neuf" } } }
let person' =
{ person with
Person.Address.Street.Num = person.Address.Street.Num + " bis" }
Qualification
Usually, we have to qualify the field to avoid a compilation error: see Person.Address.