` | Unrestricted element types, including absence markers and errors; a contract that never moves |
The first two exclude immediate `missing`, `nothing`, and `error`
elements in subtype checks. These are not recursive absence restrictions:
a list containing a missing datum is itself a collection value.
### Naming Constraints for Elements and Arguments
:::info
Element names (used in tuples, records, dictionaries) and function argument names should:
- start with a letter (`U+0041` to `U+005A` or `U+0061` to `U+007A`) or underscore (`U+005F`)
- contain only letters, digits (`U+0030` to `U+0039`), or underscores
Names that don’t follow these rules must be enclosed in backticks.
The backticks are not part of the name, they are used to escape the name.
For example:
``tuple<`1st`: integer, `2nd`: integer, `3rd`: integer>``
``record{`durée`: number, vitesse: number}``
``(`直径`: number) -> number``
If the name contains a backtick or backslash, those characters must be escaped with a backslash:
``record{`name\`with\`backticks\\and\\backslash`: integer}``
The backtick syntax is used instead of quotes to clearly distinguish identifiers from string values, following conventions from languages such as Swift and Kotlin
Element and argument names are stored and compared using Unicode Normalization Form C (NFC).
:::
## Primitive Types
A **primitive type** is a type that is not defined in terms of other types.
The Compute Engine supports the following primitive types:
| Type | Description |
| :-------------- | :----------------------------------------------------------------------------------------------- |
| `any` | The universal or top type. Every type is a subtype of, and therefore matches, `any` |
| `error` | The type of an **invalid expression**, such as `["Error"]` |
| `nothing` | The type whose only member is the symbol `Nothing`; the unit type |
| `missing` | The type whose only member is the symbol `Missing`; a position-preserving absent value |
| `never` | The type that has no values; the empty type or **bottom type**. It is a subtype of every type, which is why the empty collection — whose elements are drawn from no values at all — types as `list` and is a member of every list type |
| `unknown` | An inference marker: a type still to be inferred, which each assignment or use refines. As a set of values it restricts nothing (every type is a subtype of it), and it proves no contract (it is a subtype only of `any` and itself). |
| `expression` | The type of a symbolic expression that represents a mathematical object, such as `["Add", 1, "x"]`, a `symbol`, a `function` or a `value` |
| `symbol` | The type of a named object, for example a constant or variable in an expression such as `x` or `alpha` |
| `function` | The type of a function literal: an expression that applies some arguments to a body to produce a result, such as `["Function", ["Add", "x", 1], "x"]` |
| `value` | A data value: a scalar, collection, object, color, regular expression, or reified type. Excludes functions and symbolic expressions typed as `symbol` or `expression`. |
| `collection` | The type of a collection of values: a `list`, a `set`, a `tuple`, a `string`, a `dictionary` or a `record` |
| `indexed_collection` | The type of a collection of values that can be accessed by an index: a `tuple`, a `range`, a `string`, a `list`, a `vector`, a `matrix` or a `tensor` |
| `range` | The type of an **index span**: a contiguous, ascending run of 1-based collection indexes, such as `["Range", 2, 5]`. A `Range` has this type only when its bounds are integers, at least 1, ascending and one apart — so `["Range", 1, 10, 2]` (stepped) and `["Range", 5, 2]` (descending) are `indexed_collection` instead. Not a mathematical interval (see `Interval`) |
| `scalar` | The type of a single value: a `boolean`, a `character`, or a `number`. A `string` is **not** a scalar — it is an indexed collection of characters |
| `boolean` | The type of the symbol `True` or `False`|
| `character` | The type of exactly one user-perceived character (one Unicode grapheme cluster), such as `["CharacterFrom", "'é'"]`. A scalar with no elements, and a disjoint sibling of `string`: a character is not a one-character string, and a string is not a character. A one-character string *literal* does narrow to a character where one is expected |
| `string` | The type of a string of Unicode characters. `string <: indexed_collection`: a string can be counted, indexed (1-based) and iterated, and its elements are its grapheme clusters. It is **not** `list` — the two are siblings, because joining characters can merge them |
| `number` | The type of a numeric value |
| `residue` | The type of a residue class, an element of ℤ/nℤ, such as `["ResidueClass", 2, 5]`. A **kind type** (see [Kind Types](#kind-types)): a `value` that is not a number, disjoint from every other type except `value`, `expression`, `any` and `unknown` |
| `measurement` | The type of a measurement, a nominal value with a standard uncertainty, such as `["Measurement", 2.5, 0.1]`. A **kind type**, as `residue` is. An operator that accepts a number accepts a measurement, but a symbol declared `real` or `number` does not |
| `quantity` | The type of a quantity, a value with a physical unit, such as `["Quantity", 2, "m"]`. A **kind type**, as `residue` is. Only the arithmetic operators, the comparisons, the trigonometric functions, `Abs` and the operators with a `value` parameter accept it |
### Numeric Types
The type `number` represents all numeric values. Below it, the numeric types
are **finite by default**: `complex`, `real`, `rational`, `integer` and
`imaginary` contain only finite values, and the values that are not finite
numbers have types of their own. Every numeric value is a finite number, a
number of infinite magnitude, or the not-a-number marker, and no value is two
of those:
$$\texttt{number} = \texttt{complex} \sqcup \texttt{infinity} \sqcup \texttt{nan}$$
So a declared `real` is a **promise of finiteness**: writing `real` as a
result type says the value is never $\pm\infty$ and never $\mathrm{NaN}$.
| Type | Description |
| :-------------- | :----------------------------------------------------------------------------------------------- |
| `number` | Any numeric value: a finite number, a number of infinite magnitude, or $\mathrm{NaN}$ |
| `complex` | A finite complex number, such as $2 + 3i$. The union of `real` and `imaginary` |
| `imaginary` | A finite complex number with a real part of $0$, such as $3i$ |
| `real` | A finite real number, such as $-2.5$ |
| `rational` | A finite number that can be expressed as the quotient of two integers, such as $-\nicefrac{3}{4}$. Includes the integers |
| `integer` | A finite whole number, such as $42$ |
| `infinity` | A number of infinite magnitude, in any direction: $+\infty$, $-\infty$, the unsigned $\tilde\infty$ (`ComplexInfinity`), and mixed values such as $\infty + i$. Disjoint from `complex` |
| `nan` | The not-a-number marker $\mathrm{NaN}$. Its only supertype is `number` |
When you need exactly the signed pair $+\infty$ and $-\infty$ — the values
whose *sign* is promised, which `infinity` does not — write
`signed_infinity`. It is precisely the union of the two value types, and
`+oo | -oo` is an equivalent spelling of the same type; a union
containing both signed infinities prints under the name
(`real | signed_infinity`). The former name for this pair,
`non_finite_number`, is **retired**: that name was misleading, since
$\tilde\infty$ and $\infty + i$ are non-finite numbers yet were not
members. It is still accepted on input for one release cycle and
normalizes to `signed_infinity`, but it is never printed.
The extended real line — a real number that may be infinite — has no
one-word name: write it out as `real | +oo | -oo`. Bare `real` does
not admit $\pm\infty$, and `real | infinity` would also admit the unsigned
$\tilde\infty$.
:::warning **Deprecated: the `finite_` type names**
The names `finite_number`, `finite_complex`, `finite_real`,
`finite_rational` and `finite_integer` have been **retired**. The bare names
above now carry those meanings, so each retired name denoted the same set of
values as a bare one.
For one release cycle the type parser still accepts them and normalizes each
to its replacement — `finite_integer` to `integer`, `finite_rational` to
`rational`, `finite_real` to `real`, `finite_complex` to `complex`, and
`finite_number` to `complex` (since "any finite number" *is* the finite
complex type). They are never produced by the engine, so a type you read back
is always spelled with the bare name. Update your type strings before the
aliases are removed.
:::
Numeric types can be constrained to a specific range within a lower and upper
bound
For example `real< -1.0..1.0 >` is the type of real numbers between $-1.0$ and $1.0$, inclusive.
An non-finite endpoint can be represented by the symbol `-oo` or `+oo` or
by omitting the endpoint.
For example: `real<..1.0>` is the type of real numbers less than $1.0$,
and is equivalent to `real< -oo..1.0 >`.
To exclude an endpoint, write the inequality marker `<` next to it:
`real<0<..>` is the type of real numbers greater than $0$ (`0<..` reads
"$0 < x$"), `real<..<1>` the type of real numbers less than $1$, and
`real<0<..<1>` the open interval $0 < x < 1$. A closed endpoint has no
marker: `real<0..<1>` is $0 \le x < 1$.
On the integer tiers an open endpoint normalizes to the next closed one:
`integer<0<..>` is the same type as `integer<1..>`, the integers greater than
or equal to $1$.
Note that `complex` and `imaginary` types do not support ranges, as they are not ordered types.
Here is the type of various numeric values:
| Value | Type |
| ------------------: | :------------------ |
| $42$ | `42` |
| $-3.14$ | `-3.14` |
| $\nicefrac{1}{2}$ | `rational<0.5..0.5>` |
| $\nicefrac{1}{3}$ | `rational<0.33..0.34>` |
| $\sqrt2$ | `real<1.4..1.5>` |
| $3i$ | `imaginary` |
| $2 + 3i$ | `complex` |
| $-\infty$ | `-oo` (widens to `infinity`) |
| $\tilde\infty$ | `~oo` (widens to `infinity`) |
| $\mathrm{NaN}$ | `NaN` (widens to `nan`) |
A number literal's type is the most precise claim available — its own value
when a machine number holds it exactly, and otherwise its tier decorated
with a range that encloses the value (see
[Literal Type](#literal-type)). Every one of these types is a subtype of
the tier you would expect: `42` matches `integer`,
`rational<0.5..0.5>` matches `rational`, `real<1.4..1.5>`
matches `real`, and so on — so code that
asks *"is this an integer?"* with `.matches()` or `.isInteger` is
unaffected by the extra precision.
The last three rows are the values that are *not* finite numbers. They no
longer match `real` or `integer`: since the bare tiers are finite,
`ce.parse("-\\infty").type.matches("real")` is `false`. Ask
`matches("real | +oo | -oo")` when you mean the extended real line.
### Where Ranged Types Come From
A ranged type can appear in four ways:
- **You declare it**: `ce.declare("x", "real<-1..1>")` makes the range a
contract on `x`.
- **An assumption refines it**: after `ce.assume(ce.parse("y > 0"))`, the
type of `y` is `(real<0..>) & !0`.
- **An operator derives it**: some operations produce a result that is
provably sign-constrained, and their type says so — `|x|` for a real `x`
has type `real<0..>`, and `e^x` has type `(real<0..>) & !0`.
- **A literal carries it**: an exact value that no machine number holds —
$\nicefrac{1}{3}$, $\sqrt2$, $10^{30}+1$ — is typed by its tier plus a
compact range that encloses the value, such as `real<1.4..1.5>`
for $\sqrt2$ (see [Literal Type](#literal-type)). Constants like
`Pi` and `ExponentialE` declare a value bracket
(`real<3.141592653589793..3.141592653589794>`), so their sign
and magnitude are type facts.
- **A binder declares it**: the index of a `Sum`, `Product`, `Loop` or
comprehension whose bounds are integer literals is declared `integer`
at its binding site, on the parse route (`\sum_{k=1}^{16}`) and on the box
route (`["Limits", "k", 1, 16]`, `["Element", "k", ["Range", 1, 16]]`)
alike. The range is used inside the body — `√((k − 0.5)/16)` is real
there, so `[√((k − 0.5)/16) for k in 1..16]` is a `list` — but a
comprehension's list keeps the element type without the range:
`[k for k in 1..16]` is a `list`.
Ranges are deliberately **not** propagated through arithmetic: the sum of
two values in `real<-1..>` is not itself in `real<-1..>`, so `x + y` falls
back to the bare tier. Carrying bounds through operations is interval
arithmetic, which the type system does not attempt.
The Compute Engine Standard Library includes definitions for sets that
correspond to some numeric types.
Read more about the **sets** included in the Standard Library
## Collection Types
A collection type represents an expression that contains multiple values, such as a list, a set, or a dictionary.
The Compute Engine supports the following collection types: `set`, `tuple`,
`list` (including `vector`, `matrix` and `tensor`), `record` and `dictionary`.
### Set
A **set** is a non-indexed collection of unique values.
The type of a set is represented by the type expression `set`, where `T`
is the type of the elements of the set.
```js
ce.parse("\\{5, 7, 9\\}").type
// ➔ "set"
```
A set can have an infinite number of elements.
### Tuple
A **tuple** is an indexed collection of values, representing a fixed
number of elements.
The type of a tuple is represented by the type expression `tuple`,
where `T1`, `T2`, ... are the types of the elements of the tuple.
```js
ce.parse("(7, 5, 7)").type
// ➔ "tuple"
```
Each element type is the **tier** of the element, never a literal's own
value type: `(7, 5, 7)` is a `tuple`, not
`tuple<7, 5, 7>`, and `(\\sqrt2, 1/3)` is `tuple` even though
`ce.parse("\\sqrt2").type` is the enclosure `real<1.4..1.5>`. A component
that is not a literal keeps its own type, ranges included: with `r`
declared `real`, `(|r|, 1)` is `tuple, integer>`.
The elements of a tuple can be named: `tuple`.
If an element is named, all elements must be named and the names must be unique
when compared in Unicode Normalization Form C (NFC).
(See [Naming Constraints for Elements and Arguments](#naming-constraints-for-elements-and-arguments) for rules on element names.)
The elements of a tuple can be accessed with a one-based index or by name.
A tuple of numbers is a **point/vector in ℝⁿ**: arithmetic on it is
componentwise vector arithmetic and produces a tuple, not a list.
```js
ce.parse("(1,2) + (3,4)").evaluate(); // ➔ (4, 6)
ce.parse("3(1,2)").evaluate(); // ➔ (3, 6)
ce.parse("(4,2)/2").evaluate(); // ➔ (2, 1)
```
Adding a scalar to a tuple (`1 + (2,3)`) is an error — a scalar does not
broadcast into a point — as is multiplying two tuples (there is no implicit
dot product). A symbol declared with a tuple type participates in vector
arithmetic symbolically, and its components can be accessed with the
`.x`/`.y`/`.z` member syntax (`P.x` is `First(P)`).
For two tuples to be compatible, each element must have the same type and the
names must match. Element names live in the type, not in the value — no literal
spells them — so the comparison is made between two type expressions:
```js
ce.type("tuple")
.matches("tuple");
// ➔ true
ce.type("tuple")
.matches("tuple");
// ➔ false
```
### List, Vector, Matrix and Tensor
A **list** is an indexed collection of values, used to represent vectors,
matrices, and sequences.
The first element of a list is at index 1, the second element is at index 2, and so on.
The type of a list is represented by the type expression `list`, where `T` is the type of the elements of the list.
```js
ce.parse("\\[1, 2, 3\\]").type.toString();
// ➔ "vector" (a list of 3 integers)
```
The type of a list literal is **honest**: it reports the actual (widened)
element type and the dimensions. Since element types are covariant, the
honest type is a subtype of every broader form — `vector`
matches `vector<3>`, `vector`, `list`, and `list`.
The element type is the **tier** of the elements, never a literal's own
value type: `[0.5, 1]` is a `vector`, and `[\\sqrt2, 1/3]` is a
`vector` even though each element's own type is an enclosing range.
The same rule types a set (`{\\sqrt2}` is `set`) and a record
(`{x: 1/3}` is `record{x: rational}`).
The **empty list** has no elements, so its element type is the bottom type
`never`. Covariance then makes it a member of every list type, which is what
you want — an empty list is a valid list of anything.
```js
ce.parse("\\[\\]").type.toString();
// ➔ "list"
ce.parse("\\[\\]").type.matches("list");
// ➔ true
```
The shorthand **`list`** admits any list, whatever its elements: as a set
of values it is `list`, and as a declaration it asks inference to
refine the element type from each assignment. The explicit `list`
is the same request spelled out. The same convention applies to every bare
element-taking constructor (`set`, `dictionary`, `collection`,
`indexed_collection`) and, for its slots and fields, to bare `tuple` and
`record`.
An explicit **`list`** denotes the same set of values, but as a
declaration it is a contract that never moves: `b: list := [1, 2]`
reports `list`, while `a: list := [1, 2]` reports `list`. A
list with absent elements (a regex match result using `Missing` for an
unmatched group) fits all three spellings; it does not fit
`list`, the contract that excludes the immediate markers.
```js
ce.parse("\\[1, 2, 3\\]").type.matches("list");
// ➔ true
```
The shorthand **`vector`** is a list of numbers, equivalent to `list`:
the two types denote the same values, so each matches the other.
```js
ce.parse("\\[1, 2, 3\\]").type.matches("vector");
// ➔ true
```
A **`vector`** is a list of `n` numbers.
```js
ce.parse("\\[1, 2, 3\\]").type.matches("vector<3>");
// ➔ true
```
A **`vector`** is a list of `n` elements of type `T`, where `T` is any
type that is not a list (see [Elements and Cells](#elements-and-cells)); it is
the same type as `list`. A literal list's
element type is the widened type of its actual elements, so a literal list of
integers matches both the narrow and the broad forms:
```js
ce.parse("\\[1, 2, 3\\]").type.matches("vector");
// ➔ true
ce.parse("\\[1, 2, 3\\]").type.matches("vector");
// ➔ true
// A list with a non-integer element widens accordingly:
ce.parse("\\[1, 2.5, 3\\]").type.matches("vector");
// ➔ false (the widened element type is real)
```
Lists of non-numeric values type honestly too — a list of two colors types
`list`, not a numeric vector — so `type.matches("list")` and
similar element-type queries answer correctly. A list with dimensions can hold
values that are not numbers, if they are not lists: `[True, False]` is a
`list`, and `["a", "b"]` is a `list`. The type
`vector` is valid too: it is the same type, and it prints as
`list`.
Similarly, a **`matrix`** is a list of its rows, and the rows all have the
same length: `matrix` is a `list>`.
- The shorthand `matrix` is `matrix`, a matrix of numbers, of
rank 2 but of any dimensions. The `?` symbol is a wildcard that matches any
number of elements.
- `matrix`: A matrix whose cells have the type `T`, of any dimensions.
- `matrix`: A matrix of `n` rows and `m` columns (e.g. `matrix<3x3>`)
- `matrix`: A matrix of `n` rows and `m` columns whose cells have the
type `T` (e.g. `matrix`)
`T` is any type that is not a list: `matrix` is a 2×2 array of
booleans. The bare `matrix` has number cells. A list of rows is not always a
matrix: a `list>` can hold rows of different
lengths, so it does not match `matrix`. A declaration that must be a
matrix spells `matrix`. A nested spelling with an outer length,
such as `list^2>`, is a parse error, because its cells would
be lists.
A dimension can be `0`: `matrix` is a matrix of two empty
rows, and `matrix` is a matrix with no rows and two columns. In a dimension, `0x` always separates a zero-length axis
from the next dimension; it is never a hexadecimal prefix. The empty list
`[]` is a value of every list type whose first axis has length `0`.
And finally, a **`tensor`** is a multi-dimensional array of numbers, of any
rank (1 or more), **`tensor`** is an array of any rank whose cells have
the type `T`, and **`tensor`** is an array of that shape. The cells can be values that are not numbers, if they are
not lists: `tensor` is an array of booleans of any rank. A vector
and a matrix are tensors: `[[1, 2], [3, 4]]` matches `tensor`. A
signature that accepts a numeric array of any rank spells `tensor`, not
`list`.
#### Elements and Cells
A list type is read in one of two ways, by its form:
| Form | Spelling | `T` is the type of | Example |
| :--- | :--- | :--- | :--- |
| A list | `list`, `set` | the elements: what indexing returns | `[[1, 2], [3]]` matches `list>`, not `matrix` |
| A tensor of a known rank | `list`, `vector`, `matrix`, `tensor` | the cells: the values at the depth of the rank | `[[1, 2], [3, 4]]` is typed `matrix` |
| A tensor of any rank | `tensor`, `tensor` | the cells | `[1, 2]` and `[[1, 2], [3, 4]]` both match `tensor` |
The elements of a tensor of rank 1 are its cells; the elements of a matrix are
its rows. So a matrix of integers is a `list>` and a
`list>`, but it is not a `list`:
```js
ce.parse("\\[\\[1, 2\\], \\[3, 4\\]\\]").type.matches("list>");
// ➔ true
ce.parse("\\[\\[1, 2\\], \\[3, 4\\]\\]").type.matches("list");
// ➔ false
ce.parse("\\[\\[1, 2\\], \\[3, 4\\]\\]").type.matches("tensor");
// ➔ true
```
A list has elements and may have a length; `vector`, `matrix` and `tensor`
have cells, of any type that is not a list, and may have a shape. The bare
names `vector`, `matrix` and `tensor` mean number cells. Two rules follow:
- The cells cannot be lists. The shape is written in one place: two rows of
three integers are `matrix`. `list^2>`,
`matrix>` and `tensor>` are not valid types,
and the error names the spelling to use (`matrix`). For rows
of any lengths, write `list>`.
- A `list` has one axis at most, its length: `list` is valid,
`list` is not. Write `matrix` for two axes and
`tensor` for three or more; the error names that spelling.
Every other cell type is valid with every head: `list`,
`list`, `list`, `list^3>`,
`matrix`, `matrix` and `tensor` are valid
types. A type prints with the head of its rank: `vector<…>` (or `list<…^n>`
when the cells cannot be numbers) for rank 1, `matrix<…>` for rank 2, and
`tensor<…>` for rank 3 or more. A comparison over a 2×2 matrix `M` is typed
`matrix`. A matrix of booleans is not a numeric matrix:
`Determinant` refuses it.
### Dictionary and Record
The **dictionary** and **record** types represent a collection of key-value pairs,
where each key is a non-empty string and each value can be any type. Building a
dictionary with a key that is not a non-empty string — an empty string, a
number, `Nothing` — is an error rather than a silently dropped entry.
A concrete **record value** has a known set of keys, while a **dictionary** can
have keys that are not defined in advance. A record *type* lists the fields a
value must have; it does not forbid additional fields.
A **record** is used to represent objects and structured data with known properties.
A **dictionary** is well suited to represent hash tables or caches.
**Keys** must be unique when compared in NFC form within a dictionary or record. Keys are not ordered.
(See [Naming Constraints for Elements and Arguments](#naming-constraints-for-elements-and-arguments) for rules on key names.)
The type of a **dictionary** is represented by the type expression `dictionary`
where `T` is the type of the values.
The type of a **record** is represented by the type expression `record{K1: T1, K2: T2, ...}`,
where `K1`, `K2`, ... are the keys and `T1`, `T2`, ... are the types of the values.
For example: `record{red: integer, green: integer, blue: integer}` is a record that
contains three elements with keys `red`, `green` and `blue`, and values of type `integer`.
**Compatibility:**
- A record type `A` matches a record type `B` when every field required by `B`
is present in `A` with a compatible type. Thus a record with more fields is a
subtype of one with fewer fields (width subtyping). The order of the keys
does not matter.
- A record is compatible with a dictionary `dictionary` if each type `T1`, `T2`, ... is compatible with `T`.
```js
ce.type("record{red: integer, green: integer}")
.matches("record{red: integer, green: integer}");
// ➔ true
ce.type("record{red: integer, green: integer}")
.matches("record{red: integer, green: integer, blue: integer}");
// ➔ false
ce.type("record{red: integer, green: integer, blue: integer}")
.matches("record{red: integer, green: integer}");
// ➔ true
ce.type("record{red: integer, green: integer, blue: integer}")
.matches("dictionary");
// ➔ true
```
The `record` type is compatible with any record, and the `dictionary` type
is compatible with both records and dictionaries.
```js
ce.type("record{red: integer, green: integer}")
.matches("record");
// ➔ true
ce.type("record{red: integer, green: integer}")
.matches("dictionary");
// ➔ true
```
### Collection
The type `collection` represent any collection of values, such as a `list`,
a `set`, a `tuple`, a `string`, a `record` or a `dictionary`.
The type `collection` is a collection of values of type `T`.
The type `indexed_collection` is an indexed collection of values of type `T`,
such as a `list`, a `tuple`, a `string` (whose elements are `character`s), or
a `matrix`. It is a subtype of `collection`.
Because `string <: indexed_collection`, a signature or a `where`
constraint written over `collection` or `indexed_collection` accepts
strings. To exclude them, intersect with a negation:
`(T) -> T where T: collection & !string`.
### Broadcastable
The type `broadcastable` represents a value that is either a scalar `T` or
a collection of `T` applied element-wise — the static type of an expression
that may **broadcast** at evaluation. It is the union `T | tensor`: a `T`,
or an array of any rank whose cells are `T`. `T` is a type that cannot hold a
list: a number type, `boolean`, `string`, a tuple, `integer | missing`. For
example `broadcastable` is a number or an array of numbers,
`broadcastable` is a boolean or an array of booleans (the result of a
comparison over a matrix is a matrix of booleans), and
`broadcastable>` is a point or an array of points.
Arithmetic in the Compute Engine broadcasts over collections at run time:
`2 \cdot [1, 2, 3]` evaluates to `[2, 4, 6]`. When an operand is a visible
collection, the result has a concrete collection type (`vector<3>`,
`list`). But when the collection-ness of an operand is not statically
visible — typically a call to a function whose return type is `unknown` — the
result may be a scalar *or* a list, depending on what the call returns. Such
expressions are typed `broadcastable`:
```js
ce.declare('h', '(number) -> unknown');
ce.parse('2h(x) - 1').type;
// ➔ broadcastable
```
The subtyping rules follow from the meaning "a `T`, or a tensor of `T`":
- `T <: broadcastable` — a scalar is a valid broadcastable value
- `list <: broadcastable`, `vector <: broadcastable`,
`matrix <: broadcastable`, `range <: broadcastable`
— every list of `T` is too, and so is every array of cells of type `T`: a
vector, a matrix, a range, `matrix` (a
`broadcastable`)
- A `tuple`, a `string`, a `set` and a value typed only `indexed_collection`
are **not** subtypes of `broadcastable`. A tuple and a string bind
atomically (a broadcasting operator applied to a string receives the whole
string, not its characters), a set is not indexed, and the type
`indexed_collection` also holds tuples, so it does not say that the value
is a list
- A list of lists is **not** a `broadcastable`, because its rows can
have different lengths: `list>` does not match
`broadcastable`, while `matrix` does
- The relation is transitive: whatever is a subtype of `list` is a subtype
of `broadcastable`
- `broadcastable <: broadcastable` when `S <: T` (covariant)
- `broadcastable` is **not** a subtype of `T` or of `list` — it may be
either one, so it is neither. It is a subtype of `T | tensor`, and not of
`T | list`, since a matrix is not a `list`
A `broadcastable` expression can be used anywhere the runtime can resolve
the ambiguity: further arithmetic propagates the type, indexing with `At`
(`(2h(x)-1)[1]`) is valid, with the element type `T | tensor` (a cell, or
a row of a matrix), and compiled JavaScript handles both outcomes with a
single compiled artifact.
The type can also be used in declarations and signatures, e.g.
`ce.declare('b', 'broadcastable')` for a parameter that accepts a
number or an array of numbers of any rank and is processed element-wise.
Note that a bare undeclared symbol does **not** type as broadcastable: in
`2x`, the symbol `x` is inferred to be a number by the arithmetic itself.
Only expressions whose collection-ness genuinely cannot be resolved
statically (function calls with top-typed results, or values declared
`broadcastable`) carry the type.
## Function Signature
A **function signature** is the type of functions literals.
A function signature is represented by the type expression `(T1) -> T2`, where
`T1` is the type of the input values of the function literal and `T2` is the
type of the output value, or return type, of the function literal.
### Return Types
If the function does not return a value, the function signature is `(T) -> nothing`.
A function that never returns, has a signature of `(T) -> never`.
### Arguments
The arguments of a function are a sequence of comma-separated types surrounded
by parentheses, for example `(T1, T2, ...) -> T3`.
If there are no input arguments, the signature is `() -> T`.
For example `() -> integer` is the type of functions that return an integer
and have no input arguments.
For example `(integer, integer) -> integer` is the type of functions that map two integers to an integer.
### Named Arguments
Optionally, the input arguments can be named, for example: `(x: integer, y: integer) -> integer`.
(See [Naming Constraints for Elements and Arguments](#naming-constraints-for-elements-and-arguments) for rules on argument names.)
For example, `(x: integer) -> integer` is a function that takes a single named argument `x` of type `integer` and returns an `integer`.
### Optional Arguments
A function signature can include **optional arguments**, which are arguments
that may or may not be provided when calling the function. An optional
argument is indicated by a question mark immediately after its type.
For example `(integer, integer?) -> integer` indicates a function literal accepting
one or two integers as input and returning an integer.
If there are any optional arguments, they must be at the end of the argument list.
A function type matches a signature only when it accepts **every call that
signature permits** — its shortest and its longest. A signature with an
optional argument permits two call shapes, so a function taking only one
argument does not match it:
```js
ce.type("(integer) -> number")
.matches("(integer, integer?) -> number");
// ➔ false (cannot serve the two-argument call)
ce.type("(integer, integer?) -> number")
.matches("(integer, integer?) -> number");
// ➔ true
```
### Variadic Arguments
A function signature can include a variable number of arguments, also known as
**variadic arguments**.
Variadic arguments are indicated by a `+` or `*`
immediately after the type of the last argument. The `+` suffix indicates that
the function accepts one or more arguments of that type, while the `*` suffix
indicates that the function accepts zero or more arguments of that type.
For example `(string, integer+) -> integer` is a function that accepts a
string as a first argument followed by one or more integers and returns an integer.
To indicate that the function accepts a variable number of arguments of any
type, use `any+` or `any*`.
A variadic signature permits calls of *any* length, so no fixed-arity function
matches one — only another variadic function can:
```js
ce.type("(integer, integer) -> number")
.matches("(integer, integer+) -> number");
// ➔ false (cannot serve a call with three or more integers)
ce.type("(integer, integer*) -> number")
.matches("(integer, integer+) -> number");
// ➔ true (zero-or-more covers one-or-more)
```
This matters when a function is **stored** under a declared type. The
declaration is a contract: it tells callers which calls are legal, so whatever
is stored must handle all of them, and assigning does not reshape the declared
type. Passing a function as an **argument** to a callback slot is a different
question — see [Function Type](#function-type) below.
If a signature has a variadic argument, it must be the last argument in the list,
and it cannot be combined with optional arguments.
### Function Type
The type `function` matches any function value — any parameter shape, any
effects. It is a distinct primitive, **not** a shorthand for a signature such
as `(any*) -> unknown`.
Where types are **compared** — `.matches()`, subtyping — a written signature
constrains callbacks contravariantly: its parameter types are a promise about
what callers may pass, so `(number) -> boolean` is not a subtype of
`(unknown) -> boolean`.
Where a function value is passed as an **operand at an arrow-typed parameter
slot**, admission is by **compatibility** instead: the operand is admitted
unless it is provably unusable — not callable at all, unable to accept the
number of arguments the operator supplies, provably disjoint in a parameter
or the result, or violating the slot's effect bound. This is what lets
`CountIf(xs, IsPrime)` work over an integer collection even though
`IsPrime: (number) -> boolean` is no contravariant subtype of the
instantiated slot, and what lets a mixed-type collection map a numeric
function per element. A callback that could never work — a number-only
predicate over a `list`, a predicate that provably returns a
non-boolean — is rejected when the expression is canonicalized, with a
message naming both arrows. (Design ruling:
`docs/plans/2026-08-18-compatibility-admission-callbacks.md`.)
So: spell a callback slot as the arrow it supplies — the collection
operators read `(T) any -> boolean` for a predicate, `(T) any -> unknown`
for a key — and reserve the bare `function` for a slot whose contract the
type language cannot express (`Iterate`'s parametric accumulator,
`Tabulate`'s dimension-dependent arity).
### Effect Specifiers
A function signature can also state the **effects** of calling the function:
what it does besides returning a value. The effects go in a slot between the
argument list and the arrow.
```js
ce.type("(real) random -> real"); // may draw from the random stream
ce.type("(string) network -> string"); // performs host network I/O
ce.type("() scope -> nothing"); // mutates a binding that outlives the call
```
There are nine effect labels, a closed set:
| Label | Meaning |
| :------------ | :---------------------------------------------------------- |
| `console` | Emits host console or diagnostic output |
| `entropy` | Unseeded, non-replayable nondeterminism |
| `environment` | Reads host environment data: navigator, locale, environment variables |
| `fs_read` | Reads the host filesystem |
| `fs_write` | Writes the host filesystem |
| `network` | Performs host network I/O |
| `random` | May draw from the ambient seeded random stream |
| `scope` | May mutate a binding that outlives the call |
| `time` | Reads the host clock |
Several labels may appear in the slot, separated by spaces. They may be written
in any order; the canonical form orders them alphabetically.
```js
ce.type("(string) scope network -> string").toString();
// ➔ "(string) network scope -> string"
```
Two keywords may appear in the slot instead of labels:
- `any` means **unknown effects**. It is the top of the effect ordering: every
consumer treats it as though every label, present and future, were there. Use
it for an opaque function that cannot state what it does.
- `pure` means **no effects**, stated explicitly.
An **empty slot also means pure**. `(real) pure -> real` and `(real) -> real`
describe the same set of effects (none) and are interchangeable everywhere a
type is compared, but they are not spelled the same: `pure` is a *statement*,
and it survives serialization so that re-declaring from a serialized signature
keeps the purity contract. An empty slot states nothing, and leaves the effects
to be inferred from the function's body.
```js
ce.type("(real) pure -> real").toString();
// ➔ "(real) pure -> real"
ce.type("(real) -> real").toString();
// ➔ "(real) -> real"
```
The grammar fails closed. An unknown label, a repeated label, or `any` or `pure`
combined with anything else is a type error rather than a silently weakened
contract:
```js
ce.type("(real) rndm -> real");
// ➔ throws: Unknown effect label `rndm`
ce.type("(real) any random -> real");
// ➔ throws: `any` cannot be combined with other effect labels
```
Because argument lists are always parenthesized, the slot is positionally
isolated: an identifier there can only be an effect label, so adding a label in
a future version can never change how an existing type string parses.
#### Effects and Subtyping
Signatures are **covariant** in their effect set: a function that does less is
usable wherever a function that may do more is accepted. So `(real) -> real` is
a subtype of `(real) random -> real`, which is a subtype of `(real) any ->
real`.
In argument position this flips, which is what makes an annotated parameter a
*requirement*. A parameter typed with a bare arrow demands a **pure** callback,
and the check happens at the call boundary like any other argument check:
```js
ce.declare("integ", { signature: "((any) -> number, real, real) -> real" });
ce.box(["integ", ["Function", ["Add", "x", 1], "x"], 0, 1]).isValid;
// ➔ true
ce.box(["integ", ["Function", ["Random"], "x"], 0, 1]).isValid;
// ➔ false (incompatible-type: "(any) -> number" vs "(unknown) random -> number")
```
To tolerate an effect rather than forbid it, list it: a parameter typed `(any)
random -> number` accepts both a drawing callback and a pure one. An operand
typed `any` fails every bound — a function that will not state its effects
cannot prove their absence.
The `function` primitive is the escape hatch: it is effect-top, so it accepts
any callable whatever its effects. That is why `Map(x |-> Random(), xs)` is
accepted; the effect is not rejected, it is simply carried onto the
application.
#### Inferred and Declared Effects
For a function you define, effects are normally **inferred** from the body, and
re-inferred every time you assign a new body. A bare arrow leaves them on that
inferred track — it declares the parameter and result types without pinning the
effects:
```js
ce.declare("draw", { type: "(number) -> number" });
// Accepted: the body draws from the random stream, and the inferred effects
// are revised to `random`.
ce.assign("draw", ce.box(["Function", ["Add", ["Random"], "n"], "n"]));
// Accepted too: a pure body revises them back.
ce.assign("draw", ce.box(["Function", ["Add", "n", 1], "n"]));
```
**`scope` is the one exception: it is opt-in.** A named definition that states
no effects promises it does not write outside itself, so a body with a *proven*
escaping write — an assignment to an enclosing binding, or an `Assume` — is
refused rather than silently re-stamped. Declare the `scope` effect to allow it:
```js
ce.declare("counter", { type: "number", value: 0 });
ce.declare("fib", { type: "(number) -> number" });
// Refused: the body assigns to the enclosing `counter`.
ce.assign("fib", ce.box(["Function",
["Block", ["Assign", "counter", ["Add", "counter", 1]], "n"], "n"]));
// ➔ throws: the body writes outside the function ... which requires
// declaring the `scope` effect
// Accepted: `scope` opts in.
ce.declare("bump", { type: "(number) scope -> number" });
ce.assign("bump", ce.box(["Function",
["Block", ["Assign", "counter", ["Add", "counter", 1]], "n"], "n"]));
```
An **anonymous** literal is not gated — it has no annotation surface, and its
arrow carries the inferred `scope` honestly — so a factory that returns a
writing closure still installs under a bare arrow; the `scope` lives on the
inner arrow.
Stating the effects explicitly — a non-empty specifier, or the `pure` keyword —
turns them into a **contract** instead. Every body assigned to the symbol must
stay within it. Over-declaring is allowed (a pure body satisfies a `scope`
contract), but exceeding it is an `incompatible-type` error and the definition
is not installed:
```js
ce.declare("g", { type: "(number) pure -> number" });
ce.box(["Assign", "g", ce.box(["Function",
["Block", ["Assign", "counter", ["Add", "counter", 1]], "n"], "n"])]).evaluate();
// ➔ Error(ErrorCode("incompatible-type", "pure effects", "scope effects"))
```
This mirrors how the rest of the type system treats inference: an inferred type
is flexible and revisable, a declared one is enforced.
### Overload Sets
A function that can be called in several different ways is described by an
**intersection** of function signatures. The value inhabits every arm, that is,
it is callable at each of them.
```js
ce.declare("Draw", {
signature: "((set) -> real) & ((collection) -> any)",
evaluate: (ops) => {
/* dispatch on ops at run time */
},
});
ce.box(["Draw", ["Interval", 0, 1]]).type; // ➔ "real"
ce.box(["Draw", ["List", 1, 2, 3]]).type; // ➔ "any"
ce.box(["Draw", 5]).isValid; // ➔ false
```
Use an intersection (`&`), not a union (`|`). A union would say the value is
callable in *one* of those ways without saying which, so a call site could rely
on none of them individually.
**Parenthesize each arm.** The `->` of a signature binds the loosest of all type
operators: its return type extends as far right as possible and absorbs any
following `&` or `|`. So `(number) -> real & string` is a *single* signature
returning `real & string`, not an intersection of two signatures.
When a call is made, the arm whose parameters accept the arguments is selected.
If several arms accept them, the **most specific** one wins — above,
`Interval(0,1)` is a `set`, and since `set` is a subtype of
`collection` both arms accept it, so the first arm is chosen and the result type
is `real` rather than `any`. When the type of an argument does not decide
which arm applies — the argument is `unknown`, or its type is wider than the
parameter of one arm — the result type is the union of the results of every
arm that can apply. Declaration order does not decide. With
`f: ((integer) -> integer) & ((real) -> string)`, `ce.box(["f", "x"]).type` is
`integer | string` for `x: real` and for an undeclared `x`, while
`ce.box(["f", 3]).type` is `integer`. If no arm accepts the arguments, the call
is an error.
An overload set is a subtype of each of its arms:
```js
ce.type("((number) -> real) & ((string) -> boolean)").matches("(number) -> real");
// ➔ true
```
A symbol declared with an overload set behaves the same way:
```js
ce.declare("f", "((integer) -> integer) & ((string) -> string)");
ce.box(["f", 3]).type; // ➔ "integer"
ce.box(["f", "'a'"]).type; // ➔ "string"
ce.box(["f", true]).isValid; // ➔ false
```
Note that a single `["Function"]` literal usually cannot implement an overload
set: assigning `(x) -> x + 1` to the `f` above is rejected, because that one
body must satisfy both `(integer) -> integer` and `(string) -> string`. Overload
sets are intended for operators whose implementation dispatches on its arguments
at run time.
### Typed Function Literals
A `["Function"]` literal can declare the types of its parameters and its return
value. A parameter is annotated by writing it as `["Typed", _symbol_, _type_]`,
and a return type is ascribed by wrapping the body in a `Typed` expression. The
literal then has a **named function signature** built from these annotations:
```js
ce.box(["Function", ["Add", "x", 1], ["Typed", "x", "'integer'"]]).type;
// ➔ (x: integer) -> integer
ce.box(["Function",
["Typed", ["Add", "x", 1], "'integer'"],
["Typed", "x", "'integer'"]]).type;
// ➔ (x: integer) -> integer
```
The annotations are **authoritative** (an ascription, not a whole-body type
check): the declared types define the literal's signature directly. In strict
mode, the arguments of a typed literal are checked against the declared
parameter types when the function is applied — a mismatch produces an
`incompatible-type` error. The value the body returns is checked against the
declared return type when it is concrete (a number, a string, a character, a
boolean, or a list or tuple of these): with `g` declared
`(integer) -> integer` and the body `n / 2`, `g(4)` is `2` and `g(5)` is an
`incompatible-type` error. A symbolic value, `Missing`, `Nothing`, `NaN` and
an error pass unchanged; a real float passes under `integer` or `rational`.
Compiled code does not make this check. Assigning a typed literal to a symbol
gives that symbol the annotated signature, including the return type.
The return ascription may also be a **full signature carrying effects** — this
is how a definition states its effect contract
(`function roll(n) random -> integer { … }` lowers to a body ascribed
`"(n: unknown) random -> integer"`). Because of that reading, ascribing an
*effect-bearing function type* as a plain **return type** requires grouping
parentheses around it:
```js
// The literal's own contract: `mk` draws from the random stream.
ce.box(["Function", ["Typed", body, "'(real) random -> real'"], "x"]);
// A pure literal whose RETURN VALUE is a drawing function: group the type.
ce.box(["Function", ["Typed", body, "'((real) random -> real)'"], "x"]);
```
The same rule applies in Epsil: write
`function mk(x) -> ((real) random -> real) { … }` for the effectful *return
type*, and `function roll(n) random -> integer { … }` for the definition's own
contract. An effect-free signature never needs the parentheses — it is always
read as a return type.
Two refinements apply to this check. A **collection argument against a scalar
parameter broadcasts**: with `h` declared `(number) -> number`, `h([1, 2, 3])`
— or `h(L + 1)` for a list-valued `L` — is accepted and maps element-wise,
typing as the corresponding vector. And a **collection parameter defers what
it cannot decide**: an argument whose static type neither proves nor refutes
conformance (say, a symbol declared plain `list` passed to a `matrix`
parameter) is accepted provisionally and checked against its actual value
when the operator evaluates; only a provable mismatch (a flat `list`
can never be a matrix) errors immediately at canonicalization.
## Generic Signatures
A signature such as `(list) -> number` pins the types of a function
once and for all. Many functions are not like that: reversing a collection
returns the same kind of collection it was given, and swapping the components
of a tuple returns those components in the other order, whatever they are. A
**generic signature** states such a relation between the argument types and
the result type directly, instead of giving up and returning `unknown`.
A generic signature is a function signature followed by a **quantifier
clause**: the keyword `where`, then a comma-separated list of **type
variables**. The clause always comes last, after the effect specifier and the
return type.
```js
ce.type("(T) -> T where T"); // identity
ce.type("(tuple) -> tuple where T, U"); // swap
ce.type("(T) -> T where T: indexed_collection"); // reverse
ce.type("(list, (T) any -> U) -> list where T, U"); // map
```
Any identifier can be a type variable — there is no naming convention — and a
variable is only a variable because the clause declares it. The names are the
author's and they round-trip: `(list) -> Elem where Elem` serializes
back with `Elem`, not with a canonical letter.
```js
ce.type("(list) -> Elem where Elem").toString();
// ➔ "(list) -> Elem where Elem"
```
Because the clause is last, nothing has to terminate it — a bound is a type,
and types extend as far right as they can, which is exactly what the final
position allows: `(T, x: real) -> boolean where T: (real) -> real` reads
without punctuation. A comma starts the *next* variable, and each variable
carries its own bound, so `where T, U: number` binds `T` unbounded and
`U: number` — not both to `number`.
Within a signature, every occurrence of a quantified name is the *same*
variable, including inside a nested signature: in
`(list, (T) any -> U) -> list where T, U` the callback's parameter
type is the list's element type. Note the `any` in the callback's effect slot
— a bare arrow demands a **pure** callback (see
[Effects and Subtyping](#effects-and-subtyping)), so an operator that accepts
arbitrary callbacks must write the effect-top form.
A variable may appear in an argument or result position, in the element
position of a constructed type (`list`, `tuple`, `set`,
`collection`, `dictionary`, `broadcastable`, a vector or matrix
element, a record field), inside a nested signature, and in **one arm of a
union**:
```js
ce.type("(T | missing) -> list where T"); // an optional argument
ce.type("(list | string) -> T where T");
```
At a call, an argument matched against a union parameter takes exactly one
arm. If it takes the **ground** arm, the argument says nothing about the
variable, and the variable is solved to `never` — the narrowest member of the
family — unless another argument constrains it. If it takes the **open** arm,
that arm is matched as usual, refutation included: a `set` is not a
`list | string`.
Only **one** arm of a union may mention a variable. With two open arms nothing
at the call site says which arm a value took, so neither variable could be
solved:
```js
ce.type("(T | U) -> tuple where T, U");
// ➔ throws: unsupported-variable-position: At most one arm of a union can
// refer to a type variable, but `T | U` has 2. Nothing at a call site says
// which arm a value took, so neither variable could be solved
```
A variable may **not** appear in an intersection or a negation. An
intersection is usually a constraint written in the wrong place: constrain a
variable with a **bound** in the clause instead.
```js
ce.type("(T & number) -> T where T");
// ➔ throws: unsupported-variable-position: The type variable `T` cannot
// appear in an intersection. To constrain a type variable, declare a bound
// on it instead: `where T: number`
ce.type("(T) -> T where T: number"); // …what that author meant
```
A variable in a **cell position** reads the cells of a tensor, and a
dimension variable reads its length: `(x: list) -> T where T, N` and
`(x: vector) -> T where T` bind `T` to the cells of a tensor of rank 1, so
`first([1, 2, 3])` is an `integer`. A matrix has rank 2 and is not a value of
either pattern: `first([[1, 2], [3, 4]])` is an `incompatible-type` error. The
plain `list` is read by its elements, as every list type is (see
[List, Vector, Matrix and Tensor](#list-vector-matrix-and-tensor)): a
matrix is a list of its rows, so `(x: list) -> T where T` applied to
`[[1, 2], [3, 4]]` binds `T` to the row type and the result is a
`vector`.
`where` is a reserved word in type strings: `ce.declareType("where", …)` is
a `reserved-type-name` error.
The prefix spelling `forall T. (T) -> T` was removed in favor of the trailing
clause. A leftover prefix clause is reported with a migration message naming
the replacement rather than a generic syntax error, and `forall` itself is now
an ordinary, declarable type name.
A **type alias** can be parameterized the same way — see
[Generic Type Aliases](#generic-type-aliases).
### Instantiating at the Call Site
Nothing stays generic once it is called. At each call the engine solves the
variables against the types of the actual arguments and substitutes the
solution into the result type, so the type of an application is always
**ground** — no variable ever escapes into an expression's type.
```js
ce.declare("first", {
signature: "(indexed_collection) -> T where T",
evaluate: ([xs]) => xs.at(1),
});
ce.box(["first", ["List", 1, 2, 3]]).type; // ➔ "integer"
ce.box(["first", ["List", "'a'", "'b'"]]).type; // ➔ "string"
```
A variable that occurs at several argument positions is solved to the **join**
of what those positions contribute — the narrowest type that covers them all,
the same rule the engine uses elsewhere to type a result from several
operands:
```js
ce.declare("n", "integer");
ce.declare("r", "real");
ce.declare("pick", { signature: "(T, T) -> T where T" });
ce.box(["pick", "n", "n"]).type; // ➔ "integer"
ce.box(["pick", "n", "r"]).type; // ➔ "real"
```
Variables in element positions are solved by matching the argument's structure:
```js
ce.declare("swap", { signature: "(tuple) -> tuple where T, U" });
ce.box(["swap", ["Tuple", 1, "'a'"]]).type;
// ➔ "tuple"
```
A callback's parameter type is instantiated too, which is what makes an
annotated callback checkable and an unannotated one inferable at the right
type:
```js
ce.declare("keep", {
signature: "(list, (T) any -> U) -> list where T, U",
});
ce.box(["keep", ["List", 1, 2, 3],
["Function", ["Greater", "x", 0], "x"]]).type;
// ➔ "list"
```
If no solution satisfies every position, the call is an ordinary
`incompatible-type` error; the expected type reported is the *instantiated*
one, never variable syntax.
### Bounds
A variable can declare an **upper bound** with `T: `. Only argument
types that are a subtype of the bound are accepted, and the variable still
binds the argument's type *verbatim* — so a bounded identity preserves the
argument's kind and its dimensions:
```js
ce.declare("rev", { signature: "(T) -> T where T: indexed_collection" });
ce.box(["rev", ["List", ["List", 1, 2, 3], ["List", 4, 5, 6]]]).type;
// ➔ "matrix"
```
Violating the bound is an error naming the bound:
```js
ce.box(["rev", ["Set", 1, 2]]).isValid; // ➔ false
ce.box(["rev", ["Set", 1, 2]]).toString();
// ➔ rev(Error(ErrorCode("incompatible-type", "indexed_collection",
// "set")))
```
An unbounded variable has no bound: `where T` is shorthand for
`where T: unknown` — "a type to be inferred at each call" — and an
explicitly written `: unknown` is normalized away when the type is
serialized. An explicit `: any` bound admits the same arguments, is a
contract rather than an inference request, and survives serialization. A bound must be a **ground**
type: it cannot mention a variable, its own or another's.
```js
ce.type("(U) -> T where T, U: list");
// ➔ throws: unsupported-variable-position: The bound of the type variable
// `U` must be a ground type: `list` refers to a type variable
```
Several standard library operators are declared this way — `Identity` is
`(T) -> T where T`, `Reverse` is
`(T) -> T where T: indexed_collection` — so their results are typed from their
arguments:
```js
ce.box(["Reverse", ["List", 1, 2, 3]]).type; // ➔ "vector"
```
`Inverse` is not generic: its signature is `(matrix) -> matrix`, and its type
handler keeps the shape of the argument but gives the entries the type
`rational`, because the inverse of an integer matrix has rational entries.
`MatrixPower` with a negative exponent is typed the same way; a positive
exponent keeps the element type.
```js
ce.box(["Inverse", ["List", ["List", 1, 2], ["List", 3, 4]]]).type;
// ➔ "matrix"
ce.declare("m", "matrix");
ce.parse("m^2").type; // ➔ "matrix"
ce.parse("m^{-1}").type; // ➔ "matrix"
```
A **scalar** bound interacts with broadcasting. On an operator that
broadcasts, a collection argument is admitted against a scalar parameter —
the bound is checked against the scalar base, as it always was — and the
variable binds the argument's **element** type; the call's ordinary broadcast
wrap then puts the argument's shape back on the result. `Conjugate` and
`Chop` are `(T) -> T where T: number`, `Remainder` is
`(T, T) -> T where T: number`:
```js
ce.box(["Conjugate", ["List", 1, 2, 3]]).type;
// ➔ "vector"
ce.box(["Remainder", ["List", ["List", 1, 2], ["List", 3, 4]], 7]).type;
// ➔ "matrix"
```
Broadcasting maps all the way down to the scalar leaves, so the variable is
bound to a leaf type whatever the argument's rank. Only the kinds a broadcast
actually maps are peeled: a `set` argument is admitted but never mapped
(`Conjugate(Set(1, 2))` stays a `set`), and a tuple is atomic
(`Conjugate((1, 2))` is a `tuple`).
### Generic Overload Sets
Each arm of an [overload set](#overload-sets) carries its own clause, and each
arm must be parenthesized:
```js
ce.type("((list) -> T where T) & ((set) -> boolean where T)");
```
The parentheses are load-bearing. `where` binds looser than `&`, so an
unparenthesized clause attaches to the **whole** intersection rather than to
the arm on its left — and an intersection is not something a clause can
quantify. The two `T`s in the parenthesized form are unrelated: a clause
quantifies exactly one arm. A clause nested inside another signature is not
accepted either — quantification is top-level (or arm-level) only.
```js
ce.type("((list) -> T) & ((set) -> boolean) where T");
// ➔ throws: unsupported-variable-position: A `where` clause can only quantify
// a function signature. To constrain one arm of an overload set,
// parenthesize it: `((list) -> T where T) & …`
ce.type("(x: ((U) -> U where U)) -> integer");
// ➔ throws: unsupported-variable-position: A `where` clause can only
// quantify a top-level signature (or one arm of an overload set), not a
// nested one. Parenthesize a nested clause: `((A) -> B where A, B)`
```
### Generic Declarations and Function Literals
A generic signature can be implemented by an ordinary function body — a
`["Function"]` literal, a `x |-> …` lambda, an Epsil `function` definition —
as long as the clause is stated on the *whole signature*. There are three
spellings.
**The `function f(…)` definition form** (Epsil) puts a **type-parameter
clause** between the name and the parameter list. A parameter may carry a
ground bound, the effect specifier and the return type are unchanged, and the
clause names are usable anywhere in the head:
```plaintext
function f(x: T) -> T { x + x }
function swap(x: T, y: U) -> tuple { (y, x) }
function g(x: T, k: (T) any -> U) -> list { [k(x)] }
function tick(x: T) random -> T { x }
function h real>(k: T, x: real) -> real { k(x) }
```
A definition may state the same clause with a trailing `where` instead. The
two spellings are synonyms — `function f(…)` is `function f(…) where T` —
and the clause always comes **last**, after the effect specifier and after the
return type, in every definition form:
```plaintext
function f(x: T) -> T where T { x + x } // block, annotated
function f(x: T) where T { x + x } // block, return inferred
function tick(x: T) random -> T where T { x } // block, with effects
f(x: T) -> T where T = x + x // math definition form
```
**One binding site per declaration.** A declaration may carry a `<…>` clause
or a `where` clause, never both: `function f(x: T) -> T where T: number` is
an error, not a bounded ``.
**A full-signature annotation** states the polytype where a type is expected
and leaves the body a plain literal. There is no binder slot here, so the
`where` clause is the only spelling:
```plaintext
const f: (x: T) -> T where T = x |-> x
```
```js
ce.box(["Function", ["Add", "x", "x"], "'(x: T) -> T where T'"]).type;
// ➔ "(x: T) -> T where T"
```
**Declare, then assign** — the form to use for a generic **recursive**
function, since the body can only call the symbol once it is declared:
```js
ce.declare("nest", "(x: T, n: integer) -> T where T");
ce.assign("nest", ce.box(["Function",
["If", ["LessEqual", "n", 0], "x", ["nest", "x", ["Subtract", "n", 1]]],
"x", "n"]));
ce.box(["nest", 5, 3]).evaluate().toString(); // ➔ "5"
ce.box(["nest", 5, 3]).type; // ➔ "integer"
ce.box(["nest", "'a'", 2]).type; // ➔ "string"
```
However it is written, the declaration is what every call sees: the arguments
are checked against the clause, the bounds are enforced, and the result type
is the *instantiated* one, exactly as for a generic `evaluate` handler.
**What the body sees.** The clause quantifies the *signature*, not the body.
Inside the body a quantified parameter is an ordinary unannotated parameter:
`x: T` carries no type information there, a bound `T: number` does not make
the body see a `number`, and two parameters sharing `T` are not known to have
the same type. The clause is the **call-site contract**; the body is
canonicalized once, exactly as an untyped literal's is.
For the same reason the clause names scope over the definition's **head**
only — its parameters, its effect specifier and its return type. A body-local
type annotation naming a type variable is an unknown-type error:
```plaintext
function f(x: T) -> T { let y: T = x; y }
// ➔ Unknown type "T"
```
**The result is a trusted ascription.** Nothing verifies that the body
actually returns a value of the argument's type — as with a
[typed function literal](#typed-function-literals), the annotation *is* the
type, it is not a run-time check:
```js
ce.declare("f", "(x: T) -> T where T");
ce.assign("f", ce.box(["Function", 0, "x"]));
ce.box(["f", "'a'"]).type; // ➔ "string"
ce.box(["f", "'a'"]).evaluate().toString(); // ➔ "0"
```
**Reading a polytype return annotation.** A polytype ascribed to the body of a
literal is always the literal's **own** signature — the same reading as an
effect-bearing annotation (see [Typed Function Literals](#typed-function-literals)).
An ungrouped signature is the contract; grouping it makes it an ordinary
*return type* instead:
```js
ce.box(["Function", ["Typed", body, "'(x: number) -> number'"], "x"]).type;
// ➔ "(unknown) -> number" (the marker IS the contract)
ce.box(["Function", ["Typed", body, "'((x: number) -> number)'"], "x"]).type;
// ➔ "(unknown) -> (x: number) -> number" (it RETURNS a function)
```
That grouping escape is not available for a polytype: a literal cannot declare
that it *returns* a generic function, only that it *is* one, so a grouped
`where` clause in the body slot is a signature-marker error.
```js
ce.box(["Function", ["Typed", body, "'(T) -> T where T'"], "x"]).type;
// ➔ "(T) -> T where T" (the literal is generic)
```
The same holds in Epsil: `function mk(x) -> (T) -> T where T { … }` defines
a generic `mk`.
**Broadcasting.** A generic literal broadcasts like any other function whose
parameters are scalar: a collection argument is mapped, down to the scalar
leaves, and the result carries the argument's shape. Since the variable binds
an *element* (see [Bounds](#bounds)), a result that mentions it is the
per-element answer:
```js
ce.declare("dup", "(x: T) -> tuple where T");
ce.assign("dup", ce.box(["Function", ["Tuple", "x", "x"], "x"]));
ce.box(["dup", 5]).type;
// ➔ "tuple"
ce.box(["dup", ["List", 1, 2]]).type;
// ➔ "list>"
ce.box(["dup", ["List", 1, 2]]).evaluate().toString();
// ➔ "[(1, 1),(2, 2)]"
ce.box(["dup", ["List", ["List", 1, 2], ["List", 3, 4]]]).evaluate().toString();
// ➔ "[[(1, 1),(2, 2)],[(3, 3),(4, 4)]]"
```
**Limits.** The first four are rejected, each with its own message:
- **No partial application.** A generic function must be given all of its
arguments; currying one reports
`Partial application of a generic function is not supported`. (Solving the
supplied prefix and pruning the clause is future work.)
- **No generic clause in a multi-clause set**, in either direction: neither a
second clause added to a generic definition nor a generic clause added to an
existing one (`generic-clause-unsupported`).
- **No literal body for a generic overload set.** An intersection with a
generic arm still needs an `evaluate` handler — the same reason a single
literal cannot implement an ordinary [overload set](#overload-sets).
- **The math definition form does not take a clause.** In Epsil,
`f(x) = x` is an ordinary expression (`f < T > (x)`, then `= x`), not a
definition — only the `function` keyword form claims the `<…>` slot.
- **Compilation declines.** A generic function is not compiled — `compile()`
falls back to the (correct) interpreted evaluation for any expression that
calls one. A compiled body could not reproduce behavior that depends on the
parameter types — a collection argument that would broadcast, in
particular — so the engine declines whole-fn rather than produce a wrong
value; per-instantiation compilation is future work.
## Literal Type
A **literal type** is a type that represents a single value.
The value can be:
- a boolean: `true` or `false`
- a number, such as `42`, `-3.14`, or `6.022e23`
- a string, such as `"yellow"`,
Literal types can be used in conjunction with a union to represent a type that
can be one of multiple values, for example:
- `0 | 1` is the type of values that are either `0` or `1`.
- `"red" | "green" | "blue"` is the type of values that are either of the
strings `"red"`, `"green"` or `"blue"`.
### Number Literals Have Literal Types
The type of a number literal **is** its literal type: the most precise
claim the type system can make about it.
```js
console.info(ce.box(42).type);
// ➔ "42"
console.info(ce.box(-3.14).type);
// ➔ "-3.14"
```
A literal type is a subtype of its numeric tier — `42` matches `integer`,
`real`, and `number` — so a literal is accepted anywhere its tier is, with
no conversion and no cast. To check what kind of number an expression is,
use `.matches()` (or the `isInteger`/`isRational`/`isNumber` shortcuts),
never a string comparison of the type's name:
```js
console.info(ce.box(42).type.matches("integer"));
// ➔ true
console.info(String(ce.box(42).type) === "integer");
// ➔ false — the type's *name* is "42"
```
### How a Number Literal Is Typed
Some exact values cannot be spelled as a plain value type. The engine
therefore picks one of three forms:
1. **A machine number holds the value exactly**: the type is that value.
`ce.box(21).type` is `21`, and `ce.box(0.5).type` is `0.5`.
2. **A machine number holds the value exactly, but the value is an exact
rational**: the type is the `rational` tier with a **singleton
range**. $\nicefrac{1}{2}$ has type `rational<0.5..0.5>`. A bare
value type cannot be used here: the lattice does not class a bare
numeric value as rational, so `0.5` matches `real` but not `rational`.
3. **No machine number holds the value** — $\nicefrac{1}{3}$, $\sqrt2$,
$10^{30}+1$: the type is a compact **closed range** on the tier of the
value. Both bounds are rounded *outward* to two significant digits.
```js
console.info(ce.parse("1/3").type);
// ➔ "rational<0.33..0.34>"
console.info(ce.parse("\\sqrt2").type);
// ➔ "real<1.4..1.5>"
console.info(ce.parse("10^{30}+1").evaluate().type);
// ➔ "integer<9.9e+29..1.1e+30>"
```
The bounds of an enclosing range provably contain the exact value, so the
type never claims a value the literal does not have. The bounds also
exclude zero, which keeps the sign of the literal a type fact, and the
range is never a singleton, so no operation can mistake a bound for the
value itself.
Domain checks read these bounds. The type of $\nicefrac{1}{3}$ proves that
its magnitude is not more than $1$, so its arcsine stays real:
```js
console.info(ce.parse("\\arcsin(1/3)").evaluate().type);
// ➔ "real"
```
When the magnitude of the value is outside the range of normal machine
numbers, no sound compact range exists. The type then falls back to a
claim about the sign only:
```js
console.info(ce.parse("10^{400}").evaluate().type);
// ➔ "(integer<0..>) & !0"
```
### Literal Types Are Not Stored
A literal type belongs to the literal itself. It lives at expression
positions only. Whenever a type is **stored** — inferred for a
declaration, solved for a generic type variable, synthesized for a
collection, derived as the signature of a function literal, or recorded as
the result of an operator — the literal widens to its tier:
```js
ce.assign("k", 42);
console.info(ce.box("k").type);
// ➔ "integer" — inference stores the tier, not the value
ce.declare("identity", "(x: T) -> T where T");
console.info(ce.box(["identity", 5]).type);
// ➔ "integer" — the type variable binds the tier, never `5`
console.info(ce.box(["List", 1, 2, 3]).type);
// ➔ "vector" — cells widen to their tier
console.info(ce.parse("(\\sqrt2, 1/3)").type);
// ➔ "tuple" — components carry their tier, never the
// enclosing range each literal reports on its own
console.info(ce.box(["Function", 21]).type);
// ➔ "() -> integer" — a derived signature stores the tier
```
This is the same discipline as `const`/`let` literal types in TypeScript:
maximum precision at the expression, a reusable contract in storage.
One place the extra precision is directly useful: type errors name the
offending value. A wrong argument reports "expected `integer`, got `2.5`"
rather than "got `real`".
## Other Constructed Types
Types can be combined to form new types using a **union**, an **intersection**, or a **negation**.
### Union
A **union** is the type of values that are in either of two types.
Unions are useful when a value may be one of several possible types.
The type of a union is represented by the type expression `T1 | T2`, where `T1` and `T2` are the types of the values.
For example, `number | boolean` is the type of values that are numbers or booleans.
### Intersection
An **intersection** is the type of values that are in both of two types.
Intersections are useful when a value must satisfy multiple type constraints at once.
They can be used to model values that meet several structural or semantic requirements.
The type of an intersection is represented by the type expression `T1 & T2`, where `T1` and `T2` are the types of the values.
Intersections are most useful for extending or combining record types.
For example, `record{length: integer} & record{size: integer}` is the type of values
that are records with both a `length` and a `size` key, that is `record{length: integer, size: integer}`.
An intersection of **function signatures** describes a function that can be
called in several different ways — see [Overload Sets](#overload-sets). Each arm
must be parenthesized, since `->` binds looser than `&`.
### Negation
A **negation** represents values that are excluded from a given type.
This can be useful for excluding special cases such as `0`, `NaN`, or `Infinity`.
A type negation is represented by the type expression `!T`, where `T` is a type.
For example, `!integer` is the type of values that are not integers.
The type `integer & !0` is the type of values that are integers but not `0`.
## Matching Types
Two types can be evaluated for **compatibility**.
A type `A` matches type `B` if all values of `A` are also values of `B`, that is, if `A` is a subtype of `B`.
Matching is used for type checking and for validating function arguments.
**To check if two types are compatible**, use the `type.matches()` method.
```js
ce.type("integer").matches("number");
// ➔ true
ce.type("number").matches("integer");
// ➔ false
ce.parse("3.14").type.matches("real");
// ➔ true
```
:::warning
Do not check for type compatibility by comparing the type strings directly.
Type strings may represent refined or derived types
(e.g. `real<0..>` where you expected `real`, or the literal type of a
number), so use
`.matches()` for compatibility checks instead of strict equality.
```js
ce.parse("3.14").type === "real";
// ➔ false (the type is actually "3.14", the literal type)
ce.parse("3.14").type.matches("real");
// ➔ true
```
:::
### Compatibility of Complex Types
When checking compatibility of complex types, both structure and element types must be considered.
Compatibility of complex types follows specific rules depending on the type of structure, such as records, tuples, or lists.
#### Records
Records are compatible if they have the same keys and the values are compatible.
```js
ce.box({ dict: { red: 1, green: 2 } }).type
.matches("record{red: integer, green: integer}");
// ➔ true
```
**Width subtyping** is supported for records, meaning that a record with more keys is
compatible with a record with fewer keys.
```js
ce.box({ dict: { red: 1, green: 2, blue: 3 } }).type
.matches("record{red: integer, green: integer}");
// ➔ true
```
#### Dictionaries
Dictionaries are compatible if the values are compatible.
```js
ce.box({ dict: { red: 1, green: 2 } }).type
.matches("dictionary");
// ➔ true
```
Records are compatible with dictionaries if all the values of the record are
compatible with the dictionary's value type.
```js
ce.box({ dict: { red: 104, green: 2, blue: 37 } }).type
.matches("dictionary");
// ➔ true
ce.box({ dict: { user: { str: "Bob" }, age: 24 } }).type
.matches("dictionary");
// ➔ false
```
#### Tuples
Tuples are compatible if they have the same length and the elements are compatible.
```js
ce.parse("(1, 2, 3)").type
.matches("tuple");
// ➔ true
```
If the elements of a tuple are named, the names must match.
```js
ce.type("tuple")
.matches("tuple");
// ➔ true
ce.type("tuple")
.matches("tuple");
// ➔ false
```
#### Lists
Lists are compatible if they have the same length and the elements are compatible.
```js
ce.parse("\\[1, 2, 3\\]").type
.matches("list");
// ➔ true
```
#### Function Literals
Function literals are compatible if the input types are compatible and the
output types are compatible, specifically the output type is covariant and the
input types are contravariant.
```js
ce.type("(number) -> integer")
.matches("(integer) -> number");
// ➔ true
```
The name of the arguments of a function signature is not taken into account when
checking for compatibility.
```js
ce.type("(x: integer) -> integer")
.matches("(integer) -> integer");
// ➔ true
```
### Could a Value Be of a Type?
`matches()` asks whether **every** value of a type is a value of the target.
That is the right question for type checking, but not for classifying a value
by its shape — and the two answers differ on **union types**.
A union matches a target only if *every* one of its members does, so a union
answers `false` even when one of its members is exactly the shape you asked
about:
```js
ce.type("integer | string").matches("integer");
// ➔ false (a `string` is not an `integer`)
```
**To check whether a value of a type could be of another type**, use
`type.couldMatch()`:
```js
ce.type("integer | string").couldMatch("integer");
// ➔ true
// Unions are distributed at every depth, including inside a parameter
ce.type("list