is expression, a small set of operators and built-in functions, and user-defined
functions when you want to name and reuse a computation.
Binding a result with is
The is expression evaluates an arithmetic expression and binds the result to a
variable:
Z is W * H as “let Z be W * H”. The variable on the left must be fresh —
unbound at that point in the body. The engine evaluates the right-hand side using
values already bound (here W and H, supplied by rect), then binds the fresh Z.
Operators and precedence
Five binary operators are available:+, -, *, /, and % (remainder). They
follow ordinary arithmetic precedence — *, /, and % bind tighter than + and -
— so:
Y * Z first, then adds X. Use parentheses to override the default grouping:
Operands must share a type
A binary operator requires both operands to have the same type. You cannot add ani64 to an f64 directly, or a u32 to an i32 — there is no implicit promotion.
When operand types differ, convert one explicitly with cast (below).
The five built-in functions
XLOG provides exactly five built-in functions. There are no others:| Function | Meaning |
|---|---|
abs(X) | Absolute value of X |
min(X, Y) | The smaller of X and Y |
max(X, Y) | The larger of X and Y |
pow(X, Y) | X raised to the power Y — always returns f64 |
cast(X, type) | Convert X to another scalar type |
pow always yields an f64, whatever its operands are, because exponentiation is
inherently a floating-point operation. cast takes a type name as its second argument
and is how you bridge the same-type rule for operators:
Numeric edge cases
Arithmetic on real hardware has boundaries, and XLOG’s are defined rather than undefined:- Division by zero. Integer division by zero yields
INT64_MAX(the largest signed 64-bit value). Float division by zero yieldsNaNorInfper IEEE-754. - Integer overflow wraps around (two’s-complement), rather than trapping.
0, or an overflowing
product, silently seeds these values into your results. The idiomatic guard is to
filter the bad inputs out in the body before computing:
Float literals must be written with both an integer and a fractional part:
1.0,
not 1.; 0.5, not .5. Scientific notation such as 1e9 is not part of the literal
grammar either. Write the digits out on both sides of the decimal point.User-defined functions
When a computation recurs, name it. A function declaration isfunc, a name, a
parameter list, a return type, and a body:
: type annotations, and the return type follows ->. The body
above is a conditional: if cond then A else B chooses between two arithmetic
expressions based on a comparison. Bodies can also be plain arithmetic:
private modifier to keep a function local to its module — it will not be
exported when another file does use:
Recursion with a base case
A function may call itself, as long as it has a base case that terminates the recursion:if guard reaches the base case (N <= 0), so the recursion bottoms out rather
than descending forever.
Calling a function
Call a function anywhere an arithmetic expression is allowed. To bind the result to a fresh variable, useis. To test the result against a value you already have, use a
comparison — remember that = is an equality test, not an assignment, so both sides
must already be bound:
is binds the fresh variable D. In the second, A and B are
already bound by span, so square(A) = B keeps only the rows where B equals the
square of A.
Aggregation
Collapse many rows into one — count, sum, min, max, and logsumexp in a rule head.