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) ≔ bodystates 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) ↦ bodyis what a program calls. Eachfncompiles 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 }
}