Skip to main content
Version: 0.80.0

Type System

FScript uses static type inference.

Inference first​

You often write no type annotations:

let add x y = x + y

The checker infers (x: numeric) -> (y: numeric) -> numeric. Each call specializes the shared numeric type to int or float:

(add 1 2, add 1.5 2.6) // (3, 4.1), with type int * float

numeric is an inference meta-type, restricted to int and float. It has no separate runtime representation. Both operands of + must have the same concrete type, and the result has that type too. Mixed calls such as add 1 2.6, and nonnumeric calls such as add "a" "b", are type errors. There are no implicit numeric conversions.

The same rule applies to -, *, /, and numeric comparisons. Comparisons return bool. Remainder (%) accepts only integers.

Type hierarchy​

This tree shows how inference variables can specialize. It describes inference and type families; FScript does not use class inheritance for these types.

'a — unconstrained inference variable
├── numeric — constrained to one numeric type
│ ├── int
│ └── float
├── bool
├── string
├── unit
├── Containers
│ ├── 't list
│ ├── 't option
│ ├── 't map (string keys), 't map[key] (explicit key type)
│ └── 't task
├── Structured values
│ ├── tuples: (t1 * t2 * ...)
│ ├── records: { Field: t; ... }
│ └── declared unions
├── Functions: argument -> result
└── Type tokens: typeof DeclaredType

An unconstrained variable can resolve to any compatible type. A numeric variable can resolve only to int or float. Neither numeric type implicitly converts to the other. Container element types may themselves be inferred or constrained.

Numeric variables have separate identities: the repeated numeric labels in add share a type because x + y ties them together. Unrelated numeric parameters need not specialize alike. For example, let pair x y = (x + x, y + y) accepts pair 1 2.6.

For concrete values and operations, see Native Types.

Optional annotations​

Use annotations for clarity or constraints:

let scale (x: float) (y: float) : float = x * y
let add x (y: int) = x + y

The second function infers (x: int) -> (y: int) -> int: the annotation specializes both operands and the result. add 1.5 2 is rejected.

You can also annotate a parameter or return as numeric:

let add (x: numeric) (y: numeric) : numeric = x + y

Each annotation starts with its own numeric variable; the addition operation unifies them. The function remains usable with integers or floats on separate calls.

Function types​

Curried function types are right-associative:

  • int -> int -> int
  • equivalent to int -> (int -> int)

Common type forms​

  • int, float, bool, string, unit
  • 'a list
  • 'a option
  • tuples like (int * string)
  • record and union types

Next​

Read Structural vs Named Annotations for record annotation styles and guidance.