Choose which values a function accepts and returns, then write those types in its declaration. Use the example to read an inferred type, a union, and a function type.
Install Topaz using First Program. For an introduction to functions and local values, start with Values and Functions.
Check and run a typed function
Save this code as types.tpz in your working folder.
type Stage = "draft" | "ready"
function describe(stage: Stage, checks: int) -> string {
"{stage} after {checks} checks"
}
let checks = 3
let current: Stage = "ready"
let render: (Stage, int) -> string = describe
print(render(current, checks))In that folder, check the file and run it.
topaz check types.tpz
topaz run types.tpzThe check succeeds with exit code 0. The program prints this line.
ready after 3 checksRead the input and result types
describe accepts a Stage and an int, then returns a string. The type (Stage, int) -> string describes that same input and result. It lets render store the function and call it later.
Topaz infers int for checks from its initial value, 3. This is type inference. Writing current: Stage supplies the type explicitly, which is called a type annotation.
Stage names the union "draft" | "ready". A union accepts a value from any of its listed alternatives. Here, those alternatives are two string literals, so another string such as "waiting" fails the type check. To fix that error in types.tpz, change the value assigned to current to "draft" or "ready".
A type alias gives an existing type another name. Stage and "draft" | "ready" mean the same type. The alias adds no wrapper around the value.
Choose a type for the job
| What you need | What to use |
|---|---|
| A local value whose type is clear from its initial value | Type inference, as in let checks = 3 |
| Explicit function inputs and results | Type annotations |
| One of several types or literal values | A union |
| A function stored or passed as a value | A function type such as (int) -> bool |
| Separate types even when their fields or stored values match | A nominal record, enum, or newtype |
Nominal types are distinguished by their declarations. For example, two separately declared record types remain different even if they have identical fields. See Records & Nominal Data for construction and matching.
Use lowercase int, float, bool, and string for primitive types. Write the unit type as (), whose value is also (). The spelling unit is not a type alias.
Collections use Array<T>, Map<K, V>, and Set<T>, where the letters stand for types. For example, Array<string> holds strings. Use Option<T> for an expected missing value and Result<T, E> for success or a recoverable error.
Give empty values a type
An empty array has no elements from which to infer a type. None also needs the type of the value that could be present. These declarations supply that information.
let names: Array<string> = []
let selected: Option<int> = NoneThe first declaration makes an empty string array. The second makes an Option<int> with no value. Function parameters, declared results, and surrounding expressions can also provide the needed type. If the type remains unknown, the checker rejects the value.
Keep the type arguments exact
Write the declared number of type arguments whenever you name a type. Array<string> takes one and Map<string, int> takes two. A primitive type or a type parameter takes none. The same rule applies to aliases and user declarations.
Generic types are invariant. This means that widening a type argument does not make the collection types interchangeable. For example, an Array<int> is not an Array<int | string>.
The standard-library types template, JSONValue, File, Bytes, ByteBuffer, Path, Regex, Match, TOMLValue, URL, Date, BigInt, Decimal, and RoundingMode take no type arguments. File denotes the resource type returned by open. Programs use its operations without accessing its internal representation.
Other type rules
intandfloatdo not convert implicitly.- Put protocol bounds on the type parameters of named functions. Functions & Generics explains how these bounds permit operations in the function body.
- Recursive aliases, variance annotations, higher-rank polymorphism, and general reflection are not supported.
Continue to Functions & Generics to use these types in reusable functions. See Null, Option, Result & Faults to choose how to represent missing values and errors.