This entry is part of "The Basic Syntax of F#" series:
- The basic syntax of F# – keywords and constructs
- The basic syntax of F# – classes, interfaces, and members
- The basic syntax of F# – types
When learning F#, there are three main kinds of information about ‘types’ that you need to know. First, you need to know the names/aliases of common .Net types (e.g. that "int" means System.Int32). Second, you need to know the names of the main F#-specific types (e.g. "unit") and what these types are/do/mean. Finally, you need to know the syntactic elements used to write F# types (e.g. generics use angle brackets and function types use "->"). So this blog entry covers these useful bits of F# knowledge.
I have covered most of this information (at least in passing) in other blog entries on F#; this entry serves mostly to consolidate the stuff on ‘types’ with lots of links to more detail.
The F# names of common .Net types
Here is a brief list of common .Net types and the aliases those types have in F#:
|<arrays>||array<‘a> or ‘a array or ‘a|
There are more, but those are the most common. For a full list of basic type abbreviations, see the F# language spec.
Types specific to F#
There are a number of F#-specific types; the ones you’ll most commonly encounter are: unit, list, option, ref, tuples, Async, Map, and Set. I’ll discuss each briefly in turn.
The unit type has only one value, written "()". It is a little bit like "void", in the sense that if you have a function that you only call for side-effects (e.g. printf), such a function will have a return type of "unit". Every function takes an argument and returns a result, so you use "unit" to signify that the argument/result is uninteresting/meaningless. (See also this entry.)
The list<‘T> type is a very common type in F#. It represents an immutable, singly-linked list. List literals are written in square brackets (e.g. "[1;2;3]"). The "::" operator can be used to cons an element onto the front of a list, or in a pattern to decompose a list into its first element and the rest of the list (e.g. "head::rest"). Lists are supported by a variety of functions in the List module.
Tuples are written with commas (and usually parentheses), e.g. "(1,2,3)". The name of the type of a tuple is the names of the types of its components, separated by ‘*’s. For example, "(true,42)" has type "bool * int". See this entry for details.
The Async<‘T> type is used for representing asynchronous computations. To learn more, this blog entry is one not bad jumping off point; also check out the PDC video (eight minutes, starting at 52:20, shows off Async; the whole video is a fantastic intro to F#).
The F# Map<‘Key,’Value> and Set<‘T> types are roughly just immutable versions of the common .Net types "Dictionary<Key,Value>" and "HashSet<T>".
One other type is worth mentioning: functions. F# function types (e.g. "int -> int") do not have ‘names’ (unlike e.g. C# where you’d use a named delegate type like "Func<int,int>"). Under the hood, F# represents function types using the FSharpFunc class. So if you see "FSharpFunc" (e.g. when viewing F# code in .Net Reflector), that’s what it is.
F# syntax for expressing types
(Since F# is a type-inferred language, you write out the names of types much more rarely than you do in other languages. Often times much of the interaction you’ll have with type names comes from reading the inferred type names that come up in hover-tooltips in Visual Studio.)
As mentioned above, F# represents function types using "->" syntax; "A -> R" is a function that takes an A and returns an R (see also here).
In F#, a generic type parameter is an identifier preceded by a tick (apostrophe) character. For example, ‘a and ‘T are common names for generic parameters. As in .Net, generic types use angle bracket syntax, e.g. "Dictionary<‘Key,’Value>". When there is a single generic parameter, you’ll sometimes see it using ‘prefix’ syntax rather than angle brackets – most commonly with the F# generic types ‘list’ and ‘option’. So for example "int list" means the same thing as "list<int>", just written a different way (mentioned here).
Though you can write "array<int>" or "int array", the way you’ll most often see array types written in F# is like this: "int ". Multidimensional array types use commas in the square brackets, so e.g. a two-dimensional array of integers is "int [,]", and a 3-D array is "int [,,]".
Type annotations in F# look like "e : type" where ‘e’ is an expression or a pattern. Type annotations are most commonly used when declaring functions. An example: "let f (x:int) : int = x + 1" – the first ":int" says that the parameter ‘x’ has type ‘int’, and the second ":int" says that the return value of the function has type ‘int’. Generic parameters on functions are often implicit, as in "let id (x:’a) : ‘a = x", but can be explicit, as in "let id<‘a> (x:’a) : ‘a = x". The ‘_’ character can be used to infer a type without naming it; for example, if ‘myList’ is a "list<int>" then the expression "myList :> seq<_>" will upcast myList to a "seq<int>" because the ‘_’ was inferred as ‘int’.
There are a number of other bits of syntax in the type system that are more rarely used, so I describe them just briefly. The syntax #type roughly means "forall ‘a when a :> type"; this feature is rarely used/needed. The syntax ^a is similar to ‘a but allows for ad-hoc polymorphism ("statically resolved type variables", a la C++ templates) with interesting constraints on types; this feature is very advanced (used almost exclusively by the arithmetic operators). The ‘delegate’ keyword can be used to create .Net delegate types; this feature is only useful for interop with other .Net languages.