Reading — step 1 of 7
Learn
F# inherits ML's strong type inference — types are usually deduced from usage. Tuples are first-class, common for returning multiple values without defining a record.
Tuples
let pair = (1, "hello") // int * string
let triple = (1, "a", true) // int * string * bool
// Destructure:
let (n, s) = pair
printfn "%d %s" n s
Tuple types use *: int * string reads as "int and string." Note: * is NOT multiplication here — it's the tuple type constructor.
fst and snd
fst (1, 2) // 1
snd (1, 2) // 2
Work only on PAIRS (2-tuples). For larger tuples, destructure with patterns.
Returning multiple values
let divmod a b = (a / b, a % b)
let (q, r) = divmod 17 5
printfn "%d remainder %d" q r
Clean way to return multiple values without defining a record. Common in F#.
Type inference
F# infers types from usage:
let add x y = x + y // inferred: int -> int -> int
let greet name = "Hello, " + name // inferred: string -> string
let generic x = x // inferred: 'a -> 'a (polymorphic)
Add explicit annotations only when needed (ambiguity, documentation):
let add (x: int) (y: int) : int = x + y
let greet (name: string) : string = "Hello, " + name
Type inference quirks
Sometimes F# can't decide:
let upper s = s.ToUpper()
// Error: Lookup on object of indeterminate type — could be string, char[], etc.
Fix with annotation:
let upper (s: string) = s.ToUpper()
F#'s inference is left-to-right; sometimes the compiler hasn't seen enough to decide. Explicit types resolve.
Generic functions
let swap (x, y) = (y, x) // 'a * 'b -> 'b * 'a
let identity x = x // 'a -> 'a
let compare a b =
if a > b then 1
elif a < b then -1
else 0
// 'a -> 'a -> int when 'a : comparison
F# auto-generalizes — uses type variables ('a, 'b) when the function works for any type with the right operations.
The constraint when 'a : comparison (or others like equality, :>SomeType) appears when needed.
Inline functions
For numeric functions that should work on multiple number types:
let inline double x = x * x
// 'a -> 'a when 'a : (static member ( * ) : 'a * 'a -> 'a)
double 5 // int → int
double 3.14 // float → float
inline enables "statically resolved type parameters" — special inference for arithmetic over multiple types. Used in libraries like FSharp.Core.
Tuples vs records
// Tuple (positional, anonymous):
let point = (3.0, 4.0)
// Record (named, more readable):
type Point = { X: float; Y: float }
let point = { X = 3.0; Y = 4.0 }
Tuples for short-lived multi-value returns. Records for domain types with clear names. Don't pass tuples around in public APIs — readers can't tell what each component means.
Common mistakes
*confusion in tuple types —int * intis the type of pairs of ints, NOT multiplication.- Tuples beyond 3-4 elements — readability collapses. Use a record.
- Forgetting type inference is left-to-right — the compiler may need annotations earlier than you expect.
- Using
fst/sndon triples — they only work on pairs. For larger tuples, destructure. - Mixing F# tuples with C# Tuple<T,U> — they're different types. F# uses System.Tuple under the hood but the F# tuple syntax is preferred.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…