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.
87 lines
3.6 KiB
Plaintext
87 lines
3.6 KiB
Plaintext
;;;; (array-fill [r c] v) and (array-gen [r c] f) — a fixed array as a value.
|
|
;;;;
|
|
;;;; DISCUSS.org's "need a value-producing array constructor": (array n T) is
|
|
;;;; the zeroed array and dotimes is Unit, so neither could be the initialiser
|
|
;;;; expression of a declaration. These are expressions, so they compose where
|
|
;;;; a bracket literal does — including as a defvar's initialiser, which is the
|
|
;;;; line the note was written about.
|
|
;;;;
|
|
;;;; The dimensions are in brackets and are the same compile-time lengths the
|
|
;;;; [n T] type spelling takes: an integer or a constant's name.
|
|
|
|
(defconst rows 3)
|
|
(defconst cols 4)
|
|
|
|
;; The line from the note. A typed declaration with a computed initialiser,
|
|
;; which is the startup-lifted path a defvar already had.
|
|
(defvar grid [rows [cols u8]] (array-fill [rows cols] 255))
|
|
|
|
;; One index per dimension, i32 each, and the return type is the element type.
|
|
(defn cell [r i32 c i32] i32 (+ (* r 100) c))
|
|
|
|
(defn one [i i32] i32 (* i i))
|
|
|
|
;; Row-major order is pinned, so a generator that counts observes it: this one
|
|
;; is called once per element and answers the call number, so the array it
|
|
;; fills is 0 1 2 ... in the order the elements are written.
|
|
(defvar ticks i32)
|
|
|
|
(defn tick [r i32 c i32] i32
|
|
(set ticks (+ ticks 1))
|
|
(- ticks 1))
|
|
|
|
(defn main [] i32
|
|
;; Rank 1.
|
|
(let [a (array-fill [5] 7)]
|
|
(print (at a 0)) (print " ") (print (at a 4)) (println "")) ; 7 7
|
|
|
|
;; Rank 2, and the element type is the fill value's.
|
|
(let [b (array-fill [2 3] (f32 1.5))]
|
|
(print (at b 1 2)) (println "")) ; 1.5
|
|
|
|
;; Rank 3.
|
|
(let [c (array-fill [2 2 2] -1)]
|
|
(print (at c 0 0 0)) (print " ") (print (at c 1 1 1)) (println "")) ; -1 -1
|
|
|
|
;; A dimension may be a constant's name, exactly as in [rows [cols u8]].
|
|
(let [d (array-fill [rows cols] 1)]
|
|
(print (at d 2 3)) (println "")) ; 1
|
|
|
|
;; The generator, rank 1: element i is i*i.
|
|
(let [g (array-gen [5] one)]
|
|
(print (at g 0)) (print " ") (print (at g 3)) (print " ")
|
|
(print (at g 4)) (println "")) ; 0 9 16
|
|
|
|
;; The generator, rank 2. Element [i][j] is i*100+j, which pins the index
|
|
;; arguments: the first is the outer index and the second the inner one, and
|
|
;; a form that passed them the other way round would print 1 and 300 here.
|
|
(let [h (array-gen [rows cols] cell)]
|
|
(print (at h 0 0)) (print " ") (print (at h 0 1)) (print " ")
|
|
(print (at h 1 0)) (print " ") (print (at h 2 3)) (println "")) ; 0 1 100 203
|
|
|
|
;; Row-major, pinned. [tick] answers the call number, so the element that
|
|
;; was written first holds 0 — and with four columns, [1][0] is the fifth.
|
|
(let [t (array-gen [rows cols] tick)]
|
|
(print (at t 0 0)) (print " ") (print (at t 0 1)) (print " ")
|
|
(print (at t 1 0)) (print " ") (print (at t 2 3)) (println "")) ; 0 1 4 11
|
|
|
|
;; The defvar from the top: 255 everywhere, read back as an i32 so the
|
|
;; printed value is the number and not a byte.
|
|
(print (i32 (at grid 0 0))) (print " ")
|
|
(print (i32 (at grid 2 3))) (println "") ; 255 255
|
|
|
|
;; An array value copies, which is what makes this a value and not a view:
|
|
;; writing through the copy leaves the global alone.
|
|
(let [copy grid]
|
|
(set (at copy 0 0) (u8 1))
|
|
(print (i32 (at copy 0 0))) (print " ")
|
|
(print (i32 (at grid 0 0))) (println "")) ; 1 255
|
|
|
|
;; A zero dimension is an array with no elements, and the loop that fills it
|
|
;; runs no passes. Nothing to read, so the claim is that it compiles and the
|
|
;; program carries on.
|
|
(let [e (array-fill [0] 9)]
|
|
(println "empty ok"))
|
|
|
|
0)
|