F# is statically typed with type inference: most types are not written. Types are written in annotations (x: int), in definitions (type Name = …), in casts and in generic arguments. This page lists the types and their syntax; the class forms are on classes and objects, generics on generics and constraints and units on units of measure.
Type syntax
| Form | Meaning |
|---|---|
int, System.Int32, Name.Space.Type | a named type; a dotted path is allowed |
t1 -> t2 | a function type; int -> int -> int is int -> (int -> int) |
t1 * t2 | a tuple type; struct (t1 * t2) is a struct tuple |
t list, t array, t option, t seq | postfix application of a one-parameter type; t[] is an array, t[,] a two-dimensional one |
Map<string, int> | a generic type with arguments in angle brackets |
'a, 'T | a generic type parameter; _ asks the compiler to infer one (List<_>) |
^T | a statically resolved type parameter |
#t | a flexible type: t or any type that inherits or implements it |
{| A: int; B: string |} | an anonymous record type; struct {| … |} is a struct one |
t | null | a nullable reference type (F# 9, with nullness checking on) |
float<m/s> | a type with a unit of measure |
t1 -> t2 when …, 'T when 'T : comparison | constraints, see the generics page |
Primitive types
| F# name | .NET type | Literal |
|---|---|---|
bool | System.Boolean | true false |
byte (uint8), sbyte (int8) | Byte, SByte | 1uy 1y |
int16, uint16 | Int16, UInt16 | 1s 1us |
int (int32), uint (uint32) | Int32, UInt32 | 1 1u |
int64, uint64 | Int64, UInt64 | 1L 1UL |
nativeint, unativeint | IntPtr, UIntPtr | 1n 1un |
float (double), float32 (single) | Double, Single | 1.0 1.0f |
decimal | Decimal | 1.0m |
char, string | Char, String | 'c' "s" |
bigint | System.Numerics.BigInteger | 1I |
obj, exn | Object, Exception | |
unit | (no value but one) | () |
nativeptr<'T>, voidptr, byref<'T>, inref<'T>, outref<'T> | pointers and managed references |
These abbreviations are defined in FSharp.Core (prim-types); int is int32, float is Double, obj is Object. The same names are conversion functions: int 3.7, float 2, string 5, char 65. unit is the type of an expression that is run for its effect, and of ().
Tuples
let pair = (1, "one") // int * string
let triple : int * string * float = 1, "a", 2.0
let (a, b) = pair // a pattern takes it apart
let s = struct (1, 2) // a struct tuple: struct (int * int)
let x, y = fst pair, snd pair // fst and snd work on pairs
A reference tuple is a System.Tuple; a struct tuple is a System.ValueTuple. Tuples compare and hash structurally.
Records
A record is a set of named fields. It is a class with properties, with structural equality and comparison and a printable form. It is built with { Field = value; … }, copied with with, taken apart with patterns, and has no constructor to write.
type Person =
{ Name: string
Age: int
mutable Email: string option }
let ada = { Name = "Ada"; Age = 36; Email = None }
let older = { ada with Age = 37 } // copy and update
let { Name = n; Age = _ } = ada // a record pattern
[<Struct>]
type Point = { X: float; Y: float } // a struct record
let anon = {| Id = 1; Label = "x" |} // an anonymous record
let more = {| anon with Extra = true |}
type Endpoint = { Host: string; Port: int }
type Client = { ...Endpoint; Timeout: int } // F# 11: a record spread
- Fields are immutable unless marked
mutable; assign with<-. - The record type is found from the field names; the later definition wins when two records share labels, or an annotation picks one.
- An anonymous record needs no definition and is structural; its fields are sorted by name in its type.
- Attributes control the form:
[<Struct>],[<CLIMutable>](a default constructor and settable properties for serialisers),[<RequireQualifiedAccess>],[<ReferenceEquality>],[<NoEquality>],[<NoComparison>],[<CustomEquality; NoComparison>]. - F# 11 record spreads:
...Typein a definition adds that record’s fields;...exprin an expression supplies field values; in a field list the rightmost contribution wins.
Discriminated unions
A union is a value that is one of several cases, each with its own fields.
type Shape =
| Circle of radius: float
| Rect of width: float * height: float
| Empty
type Tree<'T> =
| Leaf
| Node of left: Tree<'T> * value: 'T * right: Tree<'T>
[<Struct>]
type Result2 =
| Good of value: int
| Bad of error: string // a struct union needs unique field names
type Id = Id of int // a single-case union
let s = Circle 1.0 // a case is a function: float -> Shape
let isEmpty = s.IsEmpty // F# 9: Is* properties
Cases are written with a leading | (optional on the first), fields after of separated by *, and may be named. A union may carry members after the cases. [<RequireQualifiedAccess>] makes callers write Shape.Circle. An empty case such as Empty is a singleton value. The cases of option, voption, list and Result are unions of this kind.
Enumerations
type Colour =
| Red = 0
| Green = 1
| Blue = 2
let c = Colour.Green
let n = int c // enums are converted explicitly
let d = enum<Colour> 2 // and back
An enum is a .NET enum over one of the integral types or char; it is not exhaustive (matching on one needs a wildcard) and cases are always qualified. [<Flags>] makes a set of bit flags combined with |||.
Option, voption and result
'T optionisSome valueorNone;'T voption(ValueSome,ValueNone) is the struct version.Result<'T, 'E>isOk valueorError e.Choice<'T1, 'T2>(up to seven cases,Choice1Of2and so on) holds one of several types.Lazy<'T>is made bylazy expr, forced by.Force()or.Value.
Collections
| Type | Literal and use |
|---|---|
'T list | immutable linked list: [1; 2; 3], [], 1 :: rest (cons), a @ b (append), [1 .. 5], [for x in xs -> x * 2] |
'T[] (array) | fixed-size mutable .NET array: [| 1; 2; 3 |], a.[0] or a[0], a.[0] <- 5; two dimensions with Array2D, a.[i, j] |
'T seq | a lazy sequence (IEnumerable<'T>): seq { 1 .. 10 }, seq { for x in xs do yield x } |
Map<'K, 'V> | immutable ordered map: Map.ofList [ 1, "a"; 2, "b" ], m.[1] |
Set<'T> | immutable ordered set: Set.ofList [3; 1; 2], set [1; 2] |
ResizeArray<'T> | System.Collections.Generic.List |
dict [ k, v ] | a read-only IDictionary; readOnlyDict for IReadOnlyDictionary |
List, array and sequence expressions may use for … in … do, yield (optional when the value is the last thing of a branch, from F# 4.7), yield!, if, match and while. Slices are written xs.[1..3], xs.[..2], xs.[2..]; from F# 6 xs[1..3] also works.
Type abbreviations, delegates and references
type Names = string list // an abbreviation: the same type
type Handler = delegate of obj * int -> unit // a .NET delegate
type Pred<'T> = 'T -> bool
let counter = ref 0
counter.Value <- counter.Value + 1 // a reference cell
let h = Handler(fun o i -> printfn "%d" i)
An abbreviation is not a new type (a signature file may not hide one). A reference cell is a record with a mutable contents field, read with .Value (the prefix ! and the assignment := are deprecated and give informational warnings from F# 6).
Null
F# types, including records, unions and lists, do not admit null as a value; .NET reference types do (null, isNull, [<AllowNullLiteral>] for F# classes). From F# 9, with <Nullable>enable</Nullable> (which defines NULLABLE), a type that accepts null is written string | null and the compiler checks it; 'T when 'T : not null is the matching constraint. Nullable<'T> value types are handled by the Nullable module and by the nullable operators of Microsoft.FSharp.Linq (?=, =?, ?<, ?>= and the rest of that table), which are used mostly in query expressions.
Equality, comparison and printing
Records, unions, tuples, lists, arrays, options and structs compare structurally with =, <>, < and the rest, and hash structurally; functions do not (a type containing one has no equality). The attributes above change this, and a class uses reference equality unless it overrides Equals. printfn "%A" value prints any value in a readable form, %O uses ToString, and sprintf builds a string.
Type inference
The compiler infers types from left to right through a file, using annotations, literals and members it has already seen; a lambda parameter may need an annotation (fun (s: string) -> s.Length) because a member lookup needs the type first. Functions are generalised automatically (let id x = x is 'a -> 'a); a value that is not a function is not generalised (the value restriction) unless it is a constructor application such as [] or has an explicit type.
Not described
Known, not yet covered
- Reference: the full rules of type inference (the specification chapter on inference procedures was not read), of equality and comparison constraints, of the value restriction and of generalisation; byrefs, spans,
NativePtrandfixedare only named (see .NET interop); struct union layout and theReuseSameFieldsInStructUnionschange of F# 9; measure-annotated numeric types. - Reference: the exact interaction of
[<CLIMutable>],[<Struct>]and records, optional record fields, record constructor syntax (MyRecord(a, b), listed for 11.2) and the rules forNullableoperators.
Unknown
- Reference: the examples were not compiled; the statements on F# 9 nullness and F# 11 spreads are from the documentation and the compiler’s feature table, not from running them.
See also
- Pattern matching and active patterns – taking values apart
- The standard library – the List, Seq, Array, Map, Set, Option and Result modules