jonnifyfsharp

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.

C1.01.1 · part 1

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.

C1.01.1 · part 2

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 int 0xFF int · 255 42L int64 42uy byte 42I bigint 3.14 float 3.14f float32 9.99m decimal 2.5e-3 float 1_000_000 int true bool 'F' char "hello" string () unit "F#".Length int · 2 the form carries the type · the suffix or quote tells F# which one
literal 42 · type int · a 32-bit signed integer, the default whole-number type
> 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

Related in this course