Dive C1.01.1
Primitive types & literals
Write the value, and F# reads the type off the way you wrote it.
F# inherits the .NET primitive types and gives each a literal form. The form
itself often carries the type: 42 is an int,
42L an int64, 42uy a
byte; 3.14 a float,
3.14f a float32, 9.99m a
decimal. This dive walks the whole set — integers and their width suffixes, the
floating and decimal kinds, bool, char,
string in its plain, verbatim, and interpolated forms, and
unit, the type with exactly one value.
Integers, floats, and decimals
The default integer is int — a 32-bit signed integer. A bare numeric literal with
no fractional part and no suffix is an int. To make it readable, F# allows
digit separators: 1_000_000 is the same value as
1000000. Other bases are written with a prefix — 0xFF is
hexadecimal (255), 0o17 octal (15), 0b1010 binary (10).
A suffix selects a different integer width or sign: 42L is
int64, 42u is uint32,
42s is int16, 42uy is
byte, and 42I is bigint, an
arbitrary-precision integer. The default floating type is float — a 64-bit IEEE
double; 3.14, 1.0, 2.5e-3, and
1e6 are all float. The f suffix
gives single precision (3.14f is float32); the
m suffix gives decimal (9.99m),
the base-10 type for money where a binary float would round wrong.
- int
- 32-bit signed integer — the default.
42,-7,1_000_000,0xFF,0o17,0b1010. - int64 / uint32
42L/42u— wider and unsigned variants by suffix.- int16 / byte
42s/42uy— narrower integer widths.- bigint
42I— arbitrary-precision integer (no overflow).- float / float32
3.14,2.5e-3/3.14f— 64-bit and 32-bit IEEE.- decimal
9.99m— base-10 fixed-scale, for exact money arithmetic.
Numeric operators are + - * / %; ** is
floating-point power (2.0 ** 10.0 is 1024.0). F# does not
convert between numeric types on its own — adding an int to a
float is a type error until one side is converted, for example with
float or int.
Bool, char, string, and unit
bool has two values, true and
false; the boolean operators are && (and),
|| (or), and not. A char is a
single character in single quotes — 'a', or an escape such as
'\n'. A string is text in double quotes, and F# offers
three forms: a plain string "hello"; a verbatim string
@"C:\verbatim", where the backslash is literal and not an escape; and an
interpolated string $"x = {x}", where the braces splice a value
into the text.
The odd one is unit. It has exactly one value, written
(), and it is what an expression evaluates to when it has no other meaningful
result — the F# stand-in for "nothing to return". A function that only performs an effect, such as
printfn, returns unit. It is not the absence of a value;
it is a real value of a real type, which is why F# can keep everything an expression.
// comparison and equality 1 = 1 // => true ( = is equality, not assignment ) 1 <> 2 // => true ( <> is not-equal ) 2 <= 3 // => true ( also < > >= ) // the three string forms "hello" // plain string @"C:\verbatim" // verbatim: the backslash is literal $"x = {x}" // interpolated: {x} splices the value of x // unit: the one value of its type () // the unit value; printfn "hi" also returns it
Six core types cover most values: whole numbers, real numbers, exact money, truth,
characters, and text — plus unit, so even "no result" is still a value with a type.
Literal → type explorer
Select a literal below. The readout shows the type F# infers for it and the exact line FSI prints when you
type the literal followed by ;;. Every entry was checked against
dotnet fsi — the grid is the verified set, not a sketch.
The primitive literals · select one
> 42;; val it : int = 42
A literal is more than a value — it is a value and a type. F# reads the type from the way the literal is written, and FSI prints both back.
The idea
Each value belongs to one type, fixed at the point it is written; there is no untyped value in F#.
F# today
9.99m is a decimal the moment it is written — the
m suffix fixes it, before any inference runs.
From literal to inference
A literal carries its type because of how it is written. But most values in a program are not literals — they come out of expressions, parameters, and function calls, where no suffix is present to read. There, F# works the type out from the surrounding use. That is type inference, the subject of the next dive: the rule that gives a type to every binding, literal or not, and usually leaves the programmer nothing to write.
The dive after that puts both under the same lamp. FSI is the REPL where you type a literal, a binding, or a whole expression, and read back its type on the spot — the tool the explorer above is a still picture of.
References
Sources
- Microsoft — F# documentation — literals, primitive types, and string forms.
- The F# Language Specification — the grammar of integer, floating, and string literals.
- FSharp.Core API documentation — the core types and operators on the primitives.
Related in this course
- C1.01.2 — Type inference — typing the values that are not literals.
- C1.01 — Values, types & FSI — the module hub and its three dives.
- Elixir — Values — the same primitives, dynamically typed on the BEAM.