# Macros and symbols

`defmacro` receives unevaluated forms and returns a form, which is
evaluated in the caller's lexical environment. Macros are unhygienic by
default, as in Common Lisp: capture is allowed and sometimes the point.
Gensyms protect introduced bindings; module identities protect a
library's private helpers.

```lisp
(defmacro twice (x)
  (once-only (x) `(+ ,x ,x)))          ; x is evaluated once

(defmacro/g! twice-explicit (x)
  `(let ((,g!value ,x)) (+ ,g!value ,g!value)))
```

## The helpers

In the [prelude](https://sexpgpu.041.io/docs/prelude.md) unless noted:

| form | behavior |
|---|---|
| `(gensym [prefix])` | a fresh uninterned symbol on every call, even across compilations in one process; a builtin |
| `(with-gensyms (name...) body...)` | binds each name to a fresh symbol while the body builds code |
| `(once-only (arg...) body...)` | in a macro body, generates bindings that evaluate each argument once, in order |
| `(defmacro/g! name lambda-list body...)` | each distinct `g!name` in the body becomes a fresh symbol per expansion |
| `(macroexpand-1 form [env])` | expands the outer macro call once; returns the form and an expansion flag |
| `(macroexpand form [env])` | expands the outer call until it is not a macro; the same two values |
| `(intern string [env])` | the symbol visible under that name in `env`, or one of this module |
| `(macrolet ((name ll body...)...) body...)` | local macros; expansion bodies see the enclosing environment |

Expansion inspection does not evaluate the result or expand subforms.
`macroexpand` defaults to its own lexical environment; `&environment`
receives the caller's. Put `(macroexpand-1 '(twice (f y)))` in a file and
[`sexpgpu eval`](https://sexpgpu.041.io/docs/explain.md#eval) prints the expansion.

## Symbols and capture

- A symbol belongs to its module, or is a fresh symbol from `gensym`. Equal
  spelling does not make two modules' private symbols equal.
- `eq` and `equal` compare identity. `symbol-name` returns the printable
  name, which is not the identity. Gensyms print as `#:name`; that is not
  reader syntax.
- An unbound ordinary symbol falls back to the prelude by name, so a module
  may define its own `when` without changing anyone else's.
- A library macro's references to its own helpers keep the library's
  identities: the caller imports the macro, never its helpers.
- Unquoted arguments keep their original identities and bindings.
- Importing a symbol shares its identity, and because functions and
  variables share a namespace, a `let` of an imported function name
  captures it.

Intentional capture across a module boundary uses `intern` with the
caller's environment:

```lisp
(defmacro aif (test then else &environment caller)
  (let ((it (intern "it" caller)))
    `(let ((,it ,test)) (if ,it ,then ,else))))

(aif 7 (+ it 1) 0) ; 8
```

## Generated declarations

Definitions register when their form executes. A macro can generate
`defun`, `defmacro`, `defmodel`, `defoptimizer`, `defvar`, `defknobs` and
`defvariant`, and another file can import the top-level names it produced.

```lisp
(defmacro define-width (name width)
  `(defvariant ,name (width ,width)))

(define-width wide 512)
(defknobs (width 128))
```

- Each form in a top-level `progn` resolves its symbols after the forms
  before it ran, exactly like separate top-level forms.
- Variants, generated or not, come before the first `defknobs`; see
  [knobs](https://sexpgpu.041.io/docs/knobs.md#defvariant).
- Local definitions stay local; imported names are not re-exported.
- Duplicate definitions and conflicting imports fail at the second binding,
  `E-EVAL-036`.

## Names in the model and the optimizer

Gensym bindings never name persistent things. A model bound with a gensym
gets no path segment; a parameter declared with a gensym gets a stable
`param.N`, and a gensym optimizer state a stable `state.N`, in declaration
order. Give persistent parts ordinary names or `(named "segment" ...)`;
see [models](https://sexpgpu.041.io/docs/models.md#paths) and [optimizers](https://sexpgpu.041.io/docs/optimizers.md#states).

## Diagnostics through macros

Generated code keeps the macro call, the definition and the template
origin. The notes survive into generated closures and show up when a later
trace fails, so an error in expanded code still points at the call you
wrote.

## A worked example

A macro generates a model constructor, `aif` from above captures `it` on
purpose, and a momentum optimizer step keeps its state under a gensym:

```lisp
(defknobs (degree 3 "number of polynomial coefficients"))

(defmacro define-polynomial (name)
  `(defmodel ,name (degree &key (policy :high))
     (defparam coefficients (zeros [degree]) :numerics policy)
     (lambda (x)
       (let ((result (zeros-like x)))
         (dotimes (i degree result)
           (setq result
                 (+ result
                    (* (sum (slice coefficients 0 i (+ i 1))) (pow x i)))))))))

(define-polynomial polynomial)
(defvar model (aif degree (polynomial it) (error "degree is required")))

(defmacro momentum-step (p g rate)
  (with-gensyms (velocity next)
    `(progn
       (defstate ,velocity (zeros-like ,p))
       (let ((,next (+ (* 0.8 ,velocity) ,g)))
         (optimizer-update (- ,p (* ,rate ,next)) ',velocity ,next)))))
```

Related: [functions](https://sexpgpu.041.io/docs/functions.md), [the prelude](https://sexpgpu.041.io/docs/prelude.md), [special forms](https://sexpgpu.041.io/docs/special-forms.md).

---

SexpGPU documentation. Every page: https://sexpgpu.041.io/llms.txt
