All pages
It is Common Lisp
The authoring language is Common Lisp, evaluated at compile time. It is a subset, not an ANSI Common Lisp implementation: if you know Common Lisp, assume it behaves that way except for the differences below.
The Lisp runs while the file compiles. Closures, macros, list processing and variable mutation construct models, optimizers and the experiment; the compiler then checks the resulting training contract. Authoring code does not have to be tensor code. No Lisp runs during training. See how it works.
What is Common Lisp here
Lexical closures, parallel let and sequential let*, setq, flet,
labels, macrolet, full lambda lists (&optional, &rest, &key,
&allow-other-keys, &aux, supplied-p variables), destructuring macro
lambda lists (&whole, &body, &environment, nested patterns),
destructuring-bind, multiple values, backquote with , and ,@,
unhygienic macros with intentional capture, fresh gensyms, intern,
macroexpand and macroexpand-1, dotimes and dolist, apply,
incf, decf, push, and numeric comparisons (/= requires pairwise
distinct arguments).
The reader
| syntax | reads as |
|---|---|
42, -3 | an i64 integer |
0.5, 1e-8 | an f64 float; write 0.5, never .5 |
"text" | a string |
name, model.embed | a symbol; case sensitive, may contain -+*/<>=!?.&_%; kebab-case by convention |
:name | a keyword |
true, false, nil | literals; nil is also the empty list |
(a b c) | a list |
[a b c] | a vector whose elements evaluate: shapes, axes, lists of models |
'x `x ,x ,@x | quote, backquote, unquote, splice |
; ... | a comment to the end of the line |
Files use the .sx extension and one layout; see fmt.
The differences
- One namespace. Functions and variables share it. Call a bound function
or model as
(f x); there is nofuncall, nofunctionand no#'. - Case sensitive names.
- Truth.
nilandfalseare false; everything else is true, including zero. See truth. - Immutable collections. Lists are proper lists; there are no dotted
pairs and no mutable cons cells.
setqrebinds a variable; it never mutates a list, a vector or a tensor. defvaris an ordinary module binding, not a special variable. Defining a name twice in one module fails.- Modules, not packages. Files import with
require.interntakes an optional macro environment instead of a package. - Numbers. Integers are
i64, floatsf64, and/always returns a float. No bignums, no ratios. Overflow and division by zero areE-EVAL-007. - Not implemented:
block,return-from,symbol-macrolet, generalizedsetf(incf,decfandpushtake variable names only), reader macros, conditions and restarts, CLOS. - Static shapes. Loops unroll into graphs; a condition on a tensor needs
where. See tensor operations. - Optimizer limits. Each optimizer update graph owns one parameter, and optimizer states start at zero. See writing optimizers.
Evaluation limits
| limit | default | code |
|---|---|---|
| compile-time work: evaluation, calls, macro expansion, quasiquote traversal, range construction | 10,000,000 units per compilation; raise with SEXPGPU_EVAL_LIMIT | E-EVAL-041 |
| recursion depth of user functions; there is no tail-call optimization | 256 frames | E-EVAL-040 |
Macro expansion chains are iterative and governed by the work budget only. The limit applies to compilation and never changes the training loop.
The pages
- Special forms: every form with its shape.
- Functions and bindings: lambda lists, scope,
apply. - Truth, iteration and values.
- Macros and symbols: capture, gensyms, generated definitions.
- Builtins and the prelude: every function.
- Tensor operations.
A worked example combines these in one complete experiment.