Language fundamentals
The src/basics/ directory contains 11 files, each isolating one language concept. This page summarizes what each file demonstrates.
Named dimensions and no implicit broadcasting
Section titled “Named dimensions and no implicit broadcasting”Tensors carry named dimensions. Operations require explicit dimension alignment; the compiler rejects shape mismatches at type-check time rather than silently broadcasting.
let x: Tensor[Batch: 32, Features: 784] = ...let w: Tensor[Features: 784, Hidden: 256] = ...let y = x @ w // result is Tensor[Batch: 32, Hidden: 256]Attempting to combine tensors along incompatible dimension names produces a compile-time error.
ADTs with exhaustive match
Section titled “ADTs with exhaustive match”Algebraic data types pair with exhaustive match expressions. The compiler enforces that every variant is handled.
type Shape = | Circle(radius: f64) | Rect(w: f64, h: f64)
fn area(s: Shape) -> f64 = match s | Circle(r) => 3.14159 * r * r | Rect(w, h) => w * hThree import forms
Section titled “Three import forms”Chelis supports qualified, selective, and glob imports:
import std.math // qualified: std.math.sqrt(x)import std.math.{sqrt, sin, cos} // selective: sqrt(x)import std.math.* // glob: everything into scopeDim polymorphism with bracketed parameters
Section titled “Dim polymorphism with bracketed parameters”Functions accept dimension parameters in brackets, enabling code that is generic over tensor shape:
fn normalize[D](x: Tensor[D]) -> Tensor[D] = x / x.sum(dim=D)The caller supplies the concrete dimension at the call site, or the compiler infers it.
No implicit precision promotion
Section titled “No implicit precision promotion”Chelis never silently widens numeric types. Mixing f32 and f64 without an explicit cast is a type error:
let a: f32 = 1.0let b: f64 = 2.0// let c = a + b // ERROR: cannot add f32 and f64let c = f64(a) + b // OK: explicit castEffects (Random) with algebraic handlers
Section titled “Effects (Random) with algebraic handlers”Side effects are tracked in the type system. The Random effect requires an algebraic handler that supplies the implementation:
fn sample() -> f64 with Random = random.uniform(0.0, 1.0)
let result = with seed(42) { sample() }The handler (with seed(...)) provides deterministic randomness, making effectful code reproducible and testable.
Linearity: consume-by-default, explicit copy, borrow
Section titled “Linearity: consume-by-default, explicit copy, borrow”Values are consumed on use. To use a value more than once, explicitly copy it or pass a borrow:
let x = Tensor.ones[N: 128]let y = x + x // ERROR: x consumed on first uselet y = copy(x) + x // OK: copy then consumefn peek(t: &Tensor[N]) -> f64 = t.sum() // borrow: no consumptionLinear types prevent accidental aliasing of mutable tensors and enable the compiler to reuse memory safely.
grad(f, wrt=w) reverse-mode AD
Section titled “grad(f, wrt=w) reverse-mode AD”Automatic differentiation is a first-class transform. grad computes reverse-mode derivatives:
fn loss(w: Tensor[D]) -> f64 = (w * w).sum()
let dw = grad(loss, wrt=w)The wrt parameter names which argument to differentiate with respect to. Higher-order derivatives compose naturally.
vmap for per-example batching
Section titled “vmap for per-example batching”vmap lifts a function written for a single example into one that operates over a batch:
fn predict(x: Tensor[Features: 784]) -> Tensor[Classes: 10] = ...
let batch_predict = vmap(predict, axis=Batch)// batch_predict: Tensor[Batch, Features: 784] -> Tensor[Batch, Classes: 10]No manual batch-dimension threading is required.
jit and realize transforms
Section titled “jit and realize transforms”jit marks a function for trace-based compilation. Computation is deferred until realize forces evaluation:
let fast_predict = jit(predict)let lazy_result = fast_predict(input) // traces, does not executelet concrete = realize(lazy_result) // executes the traced graphThis separation allows the compiler to fuse operations and optimize the computation graph before execution.
Compile-time macros
Section titled “Compile-time macros”Macros run at compile time and produce Surf AST nodes:
macro repeat_layer(n: int, layer: Expr) = for i in 0..n: emit layer
let stack = repeat_layer(6, TransformerBlock(hidden=512))Macros have access to type information and can generate code based on compile-time constants.