The trio the author decided on 2026-09-20 is now all built: def is CL's
defparameter — its initialiser runs on every daemon re-run, unguarded, so
an edited initialiser repaints the same storage on C-c C-c plus re-run —
defonce (Clojure's name for CL's defvar, per the author) initialises once
behind the .init~once. flag, and defconst stays the image.
One parse arm reads both forms; the difference is Ast.reinit, carried to
Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and
Check.check_global lifts every def initialiser — zero and literal
included — into global/<n>, so the host's startup reaches it through the
function cell and a re-evaluated def swaps it (Session's def_inits;
Emit.redefinition declares the cell for a non-sibling target). The old
defvar spelling is refused with the rename and both compiling spellings,
and every program, test, doc and editor list is swept — except sand.flan,
the author's live WIP, whose seven defvar lines are flagged in FIX.org
and keep its three dependent tests red on this branch.
(array-gen [3 4] (fn [i j] ...)) — the canonical form — was refused:
check_fn saw no (Fn ...) want and no position to take types from. But the
form knows them: one i32 index per dimension is the rank's own promise.
check_array_gen now hands an inline fn its parameter types directly, with
the annotated element type as the return want where the annotation reaches
that deep, and the return left for the body to say where it does not — so
a bare inline fn infers its element type the way a fill value does, and a
body that disagrees with an annotated element is reported at the
generator's answer, per element. Named defn generators check as before.
check_fn grows a ?gen way in for exactly this: parameter types without a
Fn want, return optional. An inferred-return body sees Unit as ctx.ret, a
rough edge left rough on purpose.
Pins: inline at rank 1 and 2, inferred element, annotated defvar, the
per-element mismatch, inline arity. The acceptance program gains the
inline form, a struct-valued fill (the per-element store is a struct
copy), and evaluated-once (a counting fill value called one time for four
elements) — riding the three existing rows, no new ones. And the FIX.org
entry the pass never wrote: dims by the [n T] rule, one index per
dimension, the Zero+While/Set/Pindex lowering with no backend edits,
composition by nesting the forms, and this fix.
DISCUSS.org's "need a value-producing array constructor": the author
wanted grid filled with 255 as part of its declaration and could not
write it. (array n T) produces the zeroed array only, and dotimes is
Unit, so it can mutate a place that already exists but cannot be the
initialiser expression -- which has to produce the whole value in one
go. The grid was declared zeroed and filled in main instead.
Two forms, both expressions, both any rank:
(array-fill [rows cols] 255) every element that value
(array-gen [rows cols] cell) every element (cell i j)
Spelled apart rather than one form dispatching on the third element's
type, because an array *of* function values is a thing to want and one
form would have to decide whether (array-fill [4] f) meant four copies
of f or four calls of it.
The dimensions are read in Parse, and that is the whole reason they are
recognised there: handed through as an ordinary call, [rows cols] is an
array literal of two names, and where those names are defconsts it is a
perfectly good two-element array of integers -- the wrong reading, and a
silent one. Read in Parse they are the same len the [n T] type spelling
takes, resolved by the same array_len, with one extra condition of their
own: the fill counts in i32 like every index in the language, so a
dimension no i32 can reach has no loop that could end.
The lowering is a loop over a slot, not an aggregate. Tast.Arr is the
node the backends have and both build it element by element from a list
as long as the array; a fill of [600 [800 u8]] is half a million
elements and there is no list to be had. So these bind the array to a
slot, zero it, run one While per dimension writing through Set of a
Pindex, and answer with the slot -- While, Set and Pindex, which is the
argument check_loop already makes for recur. Nothing new reaches a
backend and all three get the form with no edit. The value stays
value-like: the slot is the form's own, and the Local at the end copies
out the way any array-typed expression does.
Row-major is pinned, not incidental: the first dimension is the
outermost loop, and a generator that counts observes it. The fill value
and the generator value are each bound once before any loop starts, so
(array-fill [n] (next-id)) is one call and n copies of its answer.
What falls out for the defvar the note was written about, and neither
half is a carve-out:
(defvar grid [rows [cols u8]] (array-fill [rows cols] 255))
is the spelling that works -- a typed global with a computed
initialiser, which is the startup-lifted path with the init-once guard
that defvar already had, so the fill runs once and the value survives a
re-run like any other computed one. The three-element spelling means
what the 2026-09-20 rule says it means: not a type, so a dyn global, and
a typed fixed array crosses into dyn only as a view of storage that
outlives the view. A freshly built array is a temporary, so it is
refused -- by the element rule where the elements are themselves an
array, by the lifetime rule where they are one of the three scalars a
view carries. Both refusals are the ones any other temporary gets.
The type an array-fill builds never goes through resolve, so resolve's
own guard is asked again where it is built: a fixed array of function
values would be zeroed, and a zeroed function value is a null pointer.