flan/test/programs/array-fill.flan
Joseph Ferano d4def945a9 An array is a value you can write, not a place you have to fill first
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.
2026-09-20 18:34:52 +07:00

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)