Built‐in types - IS4Code/Sona GitHub Wiki

Each code element capable of storing a value has an associated type that indicates the allowed runtime type of the values stored in it. This type may be stated explicitly, but it may also be inferred partially or fully from the usage of the element, and therefore can be in many cases omitted completely.

If a named code element needs to be assigned a type manually, it usually goes after it, separated using as:

// Explicit type
let a as int = 30

// The type is inferred from the value
let b = 30

There is no difference at runtime between these two variables ‒ in the latter case, the compiler can infer that the type is also int from the value assigned to the variable. Thanks to the Hindley–Milner type system powering F#'s type inference, explicit type annotations are necessary only in edge cases when the types are too complex to be inferred, or for public APIs where the types cannot be inferred from usage or should be more specific than what is inferred.

When a part of a compound type is supposed to be inferred, it is usually done so by omitting it completely, including its surrounding syntax. For example, an arbitrary type T can be inferred in T[] (an array of T) just by writing [] alone, from list<T> by list<>, or from function(T) (a function taking a single parameter of type T) simply by writing function.

Named types

There are several types that are identified using a keyword and correspond to a .NET runtime type:

.NET type Keyword
Boolean bool
SByte int8 or sbyte
Int16 int16
Int32 int32 or int
Int64 int64
Int128 int128
IntPtr nativeint
Byte uint8 or byte
UInt16 uint16
UInt32 uint32 or uint
UInt64 uint64
UIntPtr unativeint
BigInteger bigint
Half float16
Single float32
Double float64 or float
Decimal decimal
Char char
String string
Object object
Void void
Exception exception

In expressions, these types must be followed by either a single expression (optionally in parentheses) or function call arguments. When followed by a single value, some of these types have different semantics than just calling the type's constructor:

  • Numeric types, bool, char, and string attempt a conversion of the value to the target type. This conversion may result in an exception on failure, which can be amended by using ? ‒ for example, int? x attempts a conversion of x to int and results in a value of type int? indicating whether the conversion succeeded.
  • object boxes the value (casts it to object), equivalent to calling box.
  • void evaluates the expression but results in unit, equivalent to calling ignore.

unit

The unit type is a special type with only one value ‒ unit. This type is used in all locations where a type is required but its value is meaningless, such as when returning from a function without a value.

An unrelated use of the unit keyword is also to define and refer to units of measurement.

Functions

Function types describe functions, declared using the function keyword:

function_type:
  'function' ('(' parameters_tuple_types (';' parameters_tuple_types)* ')')? ('as' type)?;

parameters_tuple_types:
  type? (',' type)*;

All parts of the syntax except the initial keyword are optional, and are inferred from usage if omitted. Aside from that, the usage matches the syntax when declaring functions:

function f(arg as int) as float
  ...
end

let fcopy as function(int) as float = f

Like in function expressions and declarations, an empty parameters tuple indicates unit, not an inferred type. This means that function() is the same as function(unit) and function(;) is the same as function() as function(), not function as function.

Tuples

Tuples aggregate several values (of possibly distinct types) together, in a particular order:

let t as (int, char, bool) = (20, 'x', true)

Tuples are constructed by grouping two or more values in parentheses, separated by commas. The built-in tuple type is likewise identified in the same way.

tuple_expression:
  '(' ('as' ('class' | 'struct' | 'new') ';'?)? expression (',' expression)+ ')';

tuple_type:
  '(' ('as' ('class' | 'struct') ';'?)? type? (',' type?)+ ')';

By default, the tuple syntax identifies a value type (struct), implemented by System.ValueTuple. This is however possible to change by using a specialized syntax, or #pragma tuple:

#pragma tuple class
// Now implemented as System.Tuple, a reference type
var t as (int, int) = (1, 2)
// Explicit syntax, same type
t = (as class; 5, 10)
// Implemented as a value type, an error
t = (as struct; 5, 10)

A value tuple cannot be assigned to a reference tuple, because such an assignment would need to form a new identity for the tuple, and could lead to surprising results from code that relies on reference equality of tuples. Assignment is however possible in the opposite direction (i.e. reference tuple to value tuple) because the target type does not distinguish identity.

In expressions, as new can be used to differentiate a tuple from other pieces of similar-looking syntax, such as function arguments. The spread syntax (..) can also be used to place the contents of one tuple into another.

In a type, individual tuple elements can be inferred simply by omitting them, thus (,) identifies a pair of arbitrarily-typed values.

Anonymous records

Records are similar to tuples, however they are not positional, instead members are identified by name. Anonymous records do not need to be declared and are determined solely by their members.

let r as {M as int, N as bool} = {as new; M = 3, N = true}

Like usual, the record type can be inferred from the usage, including the types of its members:

function f(r as {M})
  return r.M
end

Anonymous records are constructed by using curly brackets to group members. In expressions, the initial as is necessary to distinguish them from normal records.

anonymous_record_expression:
  '{' 'as' ('class' | 'struct' | 'new') ';'? name '=' expression (',' name '=' expression)* '}';

anonymous_record_type:
  '{' ('as' ('class' | 'struct') ';'?)? name ('as' type)? (',' name ('as' type)?)* '}';

By default, records are reference types, however this can be changed via #pragma record or by using the specialized syntax:

// Implemented as a reference type
var r = {as new; M = 1}
#pragma record struct
// Now as a value type, an error
r =  {as new; M = 2}

Like with tuples, value type-based anonymous record cannot be assigned to a reference type-based anonymous record, for the same reasons.

Option

An option type expresses optional presence of a value. It is created using two constructors: some value (when a value is present) and none (when a value is absent).

// Option initialized to some value
var o as int? = some 10
// Reset to no value
o = none

The option type is indicated by the ? suffix.

option_type:
  type? '?'.

The suffix can also be placed alone, indicating an optional value with its concrete type inferred from context.

By default, options are implemented using the voption type in F#, a value type. This can be changed via #pragma option:

// Implemented as a value type
var o = some 20
#pragma option class
// Now as a reference type, an error
o = some 30

The reference and value-typed option types are not mutually compatible.

⚠️ **GitHub.com Fallback** ⚠️