Type system basics
Chelis makes tensor dimensions and numeric precision explicit. The checker rejects an axis order that conflicts with a declared type and an arithmetic operation whose operands have different precision. See the type system reference for the full type surface.
Tensor types
Section titled “Tensor types”A tensor type lists its dimensions followed by its element type:
tensor[f32] -- rank-zero tensortensor[n, f32] -- one dimensiontensor[batch, seq, f32] -- two named dimensionsA rank-zero tensor[f32] is distinct from an f32 host scalar. A dimension
variable such as n is declared in a function's [...] clause. Distinct named
dimensions, such as batch and seq, do not match each other just because
they have the same extent.
def add_vec[n](x: tensor[n, f32], y: tensor[n, f32]) -> tensor[n, f32] = add(x, y)Both arguments must have the same dimension, and the result keeps it. Call
sites bind n from their inputs, so the function works with different vector
lengths.
Shapes and precision
Section titled “Shapes and precision”- Elementwise operations do not broadcast implicitly; their tensor dimension
lists must match. Use
insertto add an axis,expandto repeat a size-one axis,reshapeto specify a new shape, orpermuteto reorder axes. - Arithmetic does not promote precision implicitly. Use
cast(x, f32)when a conversion is intended. - An ordinary unsuffixed integer literal has type
i32; an ordinary unsuffixed float literal has typef32. A numeric declaration, directcast, orto_tensordtype argument can state another dtype for a literal it directly contains. A suffix such as1.0f64selects a dtype explicitly. - A bracket literal is a
List:xs = [1.0, 2.0, 3.0]has typeList f32. It becomes a tensor throughto_tensor([1.0, 2.0, 3.0], f32), which has typetensor[3, f32], or where its own binding or function result declares a tensor type, as inxs: tensor[3, f64] = [1.0, 2.0, 3.0]. Nested brackets supply a tensor's dimensions, and the declaration gives the unsuffixed elements its element dtype. A tensor parameter or acastnever converts a bracket literal. A numeric literal insideto_tensorneeds a suffix or a dtype argument; it has no default. Writeto_tensor([1.0, 2.0], f64)orto_tensor([1.0f64, 2.0f64])to bind the elements directly atf64.cast(to_tensor([1.1, 2.2], f32), f64)instead widensf32values. A dtype argument checks already-typed elements; it does not convert them.xs = [1.1, 2.2]binds an ordinaryList[f32], soto_tensor(xs)keeps thosef32values. Mixed dtypes and ragged tensor literals are rejected.
An empty list supplies no element values from which to determine a tensor's dtype. Give the list an element type before converting it:
empty_values: List[f64] = []empty_tensor = to_tensor(empty_values)Here empty_tensor has shape [0] and dtype f64, as to_tensor([], f64)
has. An unconstrained to_tensor([]) is rejected by the checker.
Dtype parameters
Section titled “Dtype parameters”A dtype bound restricts the dtypes a parameter accepts. A bound is either a
family or an explicit set. Int accepts integer dtypes; Float accepts
floating dtypes; Numeric accepts both:
def double_ints[p: Int](x: p) -> p = add(x, x)An explicit set admits exactly the dtypes it lists, which is how a declaration excludes a member its family would admit:
def widen_only[p: {f32, f64}](x: p) -> p = add(x, x)For named dimensions, rank polymorphism, generic casts, ownership, the difference between the two bound forms, and the corresponding Deep forms, see the type system reference. For effects in function types, continue to Effects.
Read-only tensor calls
Section titled “Read-only tensor calls”Comparisons, max_elem / min_elem, and unary activations (sigmoid, tanh,
silu, gelu, gelu_tanh) borrow their tensor inputs. Both operands of a comparison or
extremum remain available for a later call, including when < or > is used.
An explicit drop(x) makes any later use of that owner an error. A call with
an owned parameter consumes its argument. If an owned tensor feeds two
ordinary consuming calls, the compiler inserts a copy for the first use;
passing a borrowed tensor to an owned parameter requires an explicit copy.
Comparisons require matching dimensions and dtypes; borrowing does not
permit implicit broadcasting or promotion.
to_tensor is reserved. A definition, parameter, local binding, pattern, or
import cannot reuse the name. The checker rejects these bindings before
evaluation or compilation; this restriction also applies inside Reef packages.
Scalar ascriptions
Section titled “Scalar ascriptions”An ascription checks the value's type. (1.5f64 : f64) agrees;
(1.5f64 : f32) is a precision mismatch. The same rule applies to
value: f64 = 1.5f64 inside a block. Use an explicit cast to convert
a value, and a suffix, a declaration, a cast, or a dtype argument to choose a
literal's dtype.
Nested ascriptions retain each check.