Start

The language

Everything a .hb document can say, from a one-line definition to a recursive loop.

A .hb document is a list of declarations, one per line. Comments start with --. Every symbol has a mathematical (Unicode) spelling and an ASCII twin; the compiler accepts either and prints the Unicode one.

Definitions and doorways

def radians(d) ≔ d · π / 180          -- a definition: mathematics, inlined where it is named
fn convert(a) ↦ radians(a)            -- a doorway: becomes a C# method
  • def name(params) ≔ body states what something is. It is never a method of its own; wherever it is named, its body is substituted and reduced. A definition with no parameters is a constant: def grid ≔ [1, 2; 3, 4].
  • fn name(params) ↦ body is what a program calls. Each fn compiles to public static methods on the class named after the file, one overload per kind of value its operations accept.

A definition body is one line. Wrapping a def after ≔ is a parse error (the only exception is the arms of a match, below). Long formulas are written by naming their parts — which is also what makes them readable.

Expressions

Mathematics Hilbert ASCII Notes
definition ≔ :=  
maps to ↦ => doorways and lambdas
product · * elementwise on tensors
minus − -  
power x^n, x², x³ x^2  
square root √x sqrt x or sqrt(x)  
comparison < > ≤ ≥ = ≠ <= >= != yield 1 or 0
pi, Euler’s number π, e pi  
partial derivative ∂ u / ∂ x — needs open Prelude.Calculus
not free in ∉ notin law guards

Coefficients. An integer written directly against a term multiplies it — 2x, 2sin(x)², 3(x + 1). With a space (2 x) it is an error; between names, always write ·.

Comparisons are numbers. x > 0 is 1 where true and 0 where false, elementwise. Hilbert has no if: a choice is arithmetic over masks.

fn positivePart(x) ↦ max(x, 0)
fn clipped(x)      ↦ min(max(x, 0), 1)          -- [−2, 0.5, 3] gives [0, 0.5, 1]
fn chosen(c, a, b) ↦ c · a + (1 − c) · b        -- c is a 0/1 mask

Strings ("ij,jk->ik") appear as arguments to einsum and in word matches.

Tensor literals

def v    ≔ [1, 2, 3]              -- a vector
def grid ≔ [1, 2; 3, 4]           -- a 2×2 matrix
def same ≔ [[1, 2], [3, 4]]       -- the same matrix, nested spelling
def cube ≔ [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]   -- rank 3

fn scaledGrid(k) ↦ grid · k       -- scaledGrid(3) = [[3, 6], [9, 12]]
fn shifted(v)    ↦ grid + v       -- shifted([10, 20]) = [[11, 22], [13, 24]]

; separates rows; one row is a vector, not a 1×n matrix. Ragged literals are refused. See What is a tensor? for shapes and broadcasting.

Intrinsics

The compiler knows a small set of functions by name — elementwise maps such as sin, exp, ln, sqrt, abs, floor; the reductions minimum and maximum; triangle; the extents rows and columns; and einsum, the contraction every other operation is built from. They are listed in Intrinsics. Everything else — mean, inverse, softmax, solve — is a definition in the Prelude.

Opening libraries

open Prelude.Statistics
open Prelude.LinearAlgebra
open Theory.Scaling                -- a .hb file in a referenced project named *.Theory

open Prelude.X brings module X of the Prelude into the document, including the laws it states. Modules may open other modules. A project can also open the .hb files of a project it references, by the last segment of that project’s name: a reference to Shop.Theory is opened as open Theory.File. An opened formula is inlined, so the result does not depend on the other assembly at run time.

One name is one formula. All names — yours and those of every module you open — share one namespace. Declaring a name that already exists is error HB0001. Two names are reserved as constants and may never be used for a parameter: pi and e.

Matching

def sign3(n) ≔ match n { when 0 ↦ 0  other ↦ n / abs(n) }

match compares a value against literal arms in order; other is the fallback. Arms may also be written on separate lines — the one place a definition continues past its first line is inside a match’s braces:

def tier(n) ≔ match n {
  when 1 ↦ 10
  when 2 ↦ 20
  other ↦ 0
}

fn tierOf(n) ↦ tier(n)       -- tierOf(2) = 20, tierOf(7) = 0

Inside a frame, a text column may also be matched against words, with | for alternatives: when "RM" | "RH" ↦ 2.

Recursion instead of loops

Hilbert has no loop statement. A definition that names itself is the loop, and the compiler decides what kind of loop it is:

def down(n, acc) ≔ match n { when 0 ↦ acc  other ↦ down(n − 1, acc + 1) }   -- tail call → while
def fac(n)       ≔ match n { when 0 ↦ 1  other ↦ n · fac(n − 1) }            -- waits → explicit stack
def even(n)      ≔ match n { when 0 ↦ 1  other ↦ odd(n − 1) }                -- mutual recursion
def odd(n)       ≔ match n { when 0 ↦ 0  other ↦ even(n − 1) }

fn counted(n)   ↦ down(n, 0)     -- counted(200000) = 200000, no stack growth
fn factorial(n) ↦ fac(n)         -- factorial(10) = 3628800

Every recursion is bounded (by HilbertMaxDepth, one million turns by default) and a run that overruns names the definition. The Prelude writes its iterative algorithms this way — Newton–Schulz inversion, conjugate gradient, the QR algorithm — with the number of turns taken from the data, such as rows(a).

Families: formulas with free variables

A doorway may return a formula. The parameters before the arrow are coefficients; the one after it is the argument:

fn line(a, b) ↦ x ↦ a + b · x
fn wave(a, b) ↦ x ↦ a + b · cos(x)

From C#, Families.Wave(1, 3) returns a Template: the graph is specialised with a = 1, b = 3 folded in, simplified, and cached. Families.Line(0, 1) reduces to a single identity node. A template converts to Func<double, double> and Func<Tensor, Tensor>, and the fully applied form Families.Line(2, 3, 10) is also generated. See Calling from C#.

Laws and theorems

A law tells the compiler how to rewrite an expression; a theorem is checked at compile time by rewriting both sides:

model Arithmetic {
  law unit = x · 1 = x
  law zero = x + 0 = x
}

theorem settled : (x · 1) + 0 = x by rewrite [unit, zero]

The compiler itself knows no identities and no derivatives — they live in the Prelude’s Arithmetic, Trigonometry and Calculus modules. See Laws and derivatives.

Models, processes and frames

Beyond formulas, a document can declare stateful objects that compile to C# classes:

  • model Name(params) : Learner { weight … def train(…) { … } def predict(x) ≔ … } — a trainable model.
  • process Name(params) { draw … def advance(k) { … } } — a stochastic process with a seeded stream.
  • model Name(path) : Frame(path) { column : Scalar from "Header" … } — a typed CSV frame.

See Models and processes.

Graphs

A graph is a model whose parent is Graph: node a { k = v } and edge a → b { k = v } (↔ both ways), each attribute a number a formula reads or a text only C# reads. Its nodes are numbered as the model first names them, it inherits every algorithm in Prelude.Graphs, and its class answers them by node name (new Pipes().MaxFlow("source", "sink")). See Declaring a graph and searching it.

model Pipes : Graph {
  edge source → v1 { capacity = 16 }
  edge v1 → sink { capacity = 12 }
}