Language tour

Surf first, Deep when tools need the tree

Chelis has two syntaxes for one language. Surf is what people read and write: functions, named dimensions, pipes, algebraic data types, and typed tensor programs. Deep is the canonical representation that tools can inspect after Surf has done its job.

01 Functions are the everyday unit

Surf is the human-facing syntax. A function declares the tensor shape it returns, so callers and compiler passes agree on the shape contract.

Surf Complete source: chelis/examples/hello_tensor.ch
module HelloTensor
def main() -> tensor[n, f32] = {
  a = to_tensor([1.0, 2.0, 3.0])
  b = to_tensor([4.0, 5.0, 6.0])
  out = add(a, b)
  out
}

02 Named dimensions travel through transforms

Dimensions are not comments. process works on a feature vector, and batch_process lifts it over the batch axis while preserving the feature axis in the type.

Surf Complete source: chelis/examples/vmap_relu.ch
def process(x: tensor[features, f32]) -> tensor[features, f32] = relu(x)
def batch_process(xs: tensor[batch, features, f32]) -> tensor[batch, features, f32] = xs |> vmap(process, axis=0)

03 Pipes keep tensor flow readable

The pipe operator passes the value on the left into each stage. That makes small activation chains and larger model blocks read in execution order.

Surf Source slice: basics/pipeandmatch.ch
def pipeline(x: &tensor[n, f32]) -> tensor[n, f32] = x |> relu |> sigmoid
Surf Source slice: capstone/transformerblock.ch
attn_out = probs |> matmul(v) |> matmul(wo)
resid1 = add(x, attn_out)
ff_out = matmul(matmul(resid1, ff1), ff2)
add(resid1, ff_out)

04 ADTs and match make cases explicit

Surf supports algebraic data types and pattern matching. The compiler sees both variants of Activation, and match arms make the control flow visible in the source.

Surf Source slice: basics/pipeandmatch.ch
type Activation =
  | Relu
  | Sigmoid
def activate(act: Activation, x: &tensor[n, f32]) -> tensor[n, f32] = {
  match act with {
    | Relu => relu(x)
    | Sigmoid => sigmoid(x)
  }
}

05 The type system rejects shape drift

Tensor widths, axes, and scalar element types are part of signatures and intermediate expressions. A wrong matmul width or bias shape is a type error instead of a backend crash.

Surf Source slice: capstone/linreg.ch
def predict(x: tensor[64, 64, f32], w: tensor[64, 1, f32], b: tensor[1, f32]) -> tensor[64, 1, f32] = x |> matmul(w) |> add(expand(b, 0, 64))
Surf Source slice: capstone/transformerblock.ch
def block(x: tensor[seq, 256, f32], wq: tensor[256, 64, f32], wk: tensor[256, 64, f32], wv: tensor[256, 64, f32], wo: tensor[64, 256, f32], ff1: tensor[256, 1024, f32], ff2: tensor[1024, 256, f32]) -> tensor[seq, 256, f32] = {
  q = matmul(x, wq)
  k = matmul(x, wk)
  v = matmul(x, wv)
  scores = matmul(q, permute(k, 1, 0))
  probs = softmax(scores, 1)
  attn_out = probs |> matmul(v) |> matmul(wo)
  resid1 = add(x, attn_out)
  ff_out = matmul(matmul(resid1, ff1), ff2)
  add(resid1, ff_out)
}

06 Deep is the canonical machine form

After Surf is parsed, Chelis can lower to Deep: a stable s-expression tree for compiler tooling, shell integration, tests, and round-tripping back to Surf.

Surf Complete source: chelis-surf/tests/fixtures/pipe.ch
def process(x: tensor[features, f32]): tensor[features, f32] =
  x |> normalize |> relu |> softmax
Deep Complete source: chelis-deep/tests/fixtures/pipeline.dp
(def {}
  process
  (fn {}
    (params {} x)
    (pipe {} (var {} x) (var {} normalize) (var {} relu) (var {} softmax))))

These snippets are static page examples. For larger listings, use the examples page; for the full reference, use the docs.