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.
73 lines
2.5 KiB
Plaintext
73 lines
2.5 KiB
Plaintext
;;;; defer in a let whose extent is the function body.
|
|
;;;;
|
|
;;;; A let is not a frame here: its bindings are function slots like any other,
|
|
;;;; and nothing is released at scope exit (spec-memory.md, "When storage is
|
|
;;;; released"). So a let at the top level of a function body has exactly the
|
|
;;;; function's extent, a defer written in it always registers, and it is as
|
|
;;;; safe as one written at the top level. A let nested inside such a let has
|
|
;;;; the same extent and the same permission.
|
|
;;;;
|
|
;;;; The numbers differ per failure, so a wrong answer names its own cause.
|
|
|
|
(defonce order i64)
|
|
|
|
(defn note [n i32] () (set order (+ (* order 10) (i64 n))))
|
|
|
|
;;; One defer in a let, with the acquisition it is paired with above it. This
|
|
;;; is the shape the relaxation exists for: acquire, defer the release beside
|
|
;;; it, then use it.
|
|
(defn one [] i64
|
|
(let [a 1]
|
|
(defer (note a))
|
|
(note 9))
|
|
order)
|
|
|
|
;;; Two resources in one let. This is the case a flag granted once per block
|
|
;;; instead of once per form gets wrong: the first defer registers and the
|
|
;;; second is refused.
|
|
(defn two [] ()
|
|
(let [a 1 b 2]
|
|
(defer (note a))
|
|
(defer (note b))
|
|
(note 9)))
|
|
|
|
;;; A nested let still has the function's extent, so a defer in it registers
|
|
;;; too — and it registers *later* than the outer one, so it runs first.
|
|
(defn nested [] ()
|
|
(let [a 1]
|
|
(defer (note a))
|
|
(let [b 2]
|
|
(defer (note b))
|
|
(note 9))))
|
|
|
|
;;; Registration order is one order across the boundary: a defer at the top
|
|
;;; level and a defer inside a let interleave by where they are written, not by
|
|
;;; which construct they are in. Written 1, 2, 3; run 3, 2, 1.
|
|
(defn mixed [] ()
|
|
(defer (note 1))
|
|
(let [x 2]
|
|
(defer (note x))
|
|
(defer (note 3))
|
|
(note 9)))
|
|
|
|
;;; An early return runs them too, and innermost-first, exactly as it does for
|
|
;;; a defer at the top level. The let's binding is still live when the defer
|
|
;;; reads it, because the slot is the function's.
|
|
(defn early [] i64
|
|
(let [a 1]
|
|
(defer (note a))
|
|
(let [b 2]
|
|
(defer (note b))
|
|
(return 7)))
|
|
0)
|
|
|
|
(defn main [] i32
|
|
(set order 0) (print (one)) (println "") ; 9 — read before the defer runs
|
|
(set order 0) (one) (print order) (println "") ; 91
|
|
(set order 0) (two) (print order) (println "") ; 921 — reverse of writing
|
|
(set order 0) (nested)(print order) (println "") ; 921
|
|
(set order 0) (mixed) (print order) (println "") ; 9321
|
|
(set order 0) (print (early)) (println "") ; 7
|
|
(print order) (println "") ; 21
|
|
0)
|