flan/test/programs/destructure.flan
Joseph Ferano 96ab4c9cf0 Retire the per-type printers, since print says all of it
print-str, print-i64, print-f64, print-bytes, print-line and newline leave
the prelude. print and println are the whole printing surface now, and print
is the better call at every one of the sites that used them: it is the same
structural walk without the newline, so the no-newline case the family was
kept for is covered, and it takes the value as it is. The old print-i64
forced an explicit (i64 x) at every call site, because this language widens
nothing implicitly; that cast is gone from 127 places.

Dropping it moves one answer. hash-grid returns u64, and the cast through
the signed printer showed sand-headless's hash as -2851001042534928384.
print routes a u64 through flan_u64_to_bytes, so it now prints
15595743031174623232 — the same 64 bits, read as the unsigned number they
are. The pinned expectation follows the correction.

test-flan-dev.el and test_session.ml both reached for print-line as "a name
the prelude has"; they reach for rand-seed instead.
2026-09-12 05:32:25 +07:00

109 lines
3.9 KiB
Plaintext

;;;; Destructuring in a let: Clojure's binding forms over Flan's shapes.
;;;;
;;;; A struct stands in for Clojure's map, so {:keys [x y]} and {inner :field}
;;;; read fields off one; a fixed array stands in for its sequence, so [a b]
;;;; and [a b & rest] read elements out of one. None of it is a new form: it
;;;; all desugars in parse.ml into the Let, (.field x), at and slice that were
;;;; already there, which is why this program is the test that it works — the
;;;; typed IR has nothing in it a pattern could be hiding in.
;;;;
;;;; The case that matters most here is `calls`. A pattern binds several names
;;;; from one value, and that value is bound to a temporary *first*, so a
;;;; pattern over a call calls it once. Delete the temporary and every name
;;;; re-evaluates the initialiser: this program prints the call count, so that
;;;; mistake changes the output instead of hiding in it.
(defstruct Point [x i32 y i32])
(defstruct Line [a Point b Point])
(defvar calls i32)
(defn make-point [] Point
(set calls (+ calls 1))
(Point {:x 3 :y 4}))
(defn show2 [label string a i32 b i32]
(print label)
(print " ")
(print a)
(print " ")
(print b)
(println ""))
(defn main [] i32
;; :keys, the common case: one name per field, spelled as the field is.
(let [{:keys [x y]} (Point {:x 1 :y 2})]
(show2 "keys" x y))
;; The pair form, which is what renames and what nests — a :keys entry is a
;; field name and never a pattern.
(let [{a :x b :y} (Point {:x 10 :y 20})]
(show2 "pairs" a b))
(let [l (Line {:a (Point {:x 5 :y 6}) :b (Point {:x 7 :y 8})})]
(let [{{:keys [x y]} :b} l]
(show2 "nested" x y))
;; A pattern may shadow the very name it destructures, because the value is
;; read into a temporary before any of the names are bound.
(let [{l :a} l]
(show2 "shadow" (.x l) (.y l))))
;; A later binding sees an earlier pattern's names, as in any let.
(let [{:keys [x]} (Point {:x 100 :y 0})
doubled (* x 2)]
(show2 "sequential" x doubled))
;; A fixed array names every element. The count is checked against the type,
;; so [a b] over a [3 i32] is a compile error and not a silent prefix.
(let [xs [11 22 33]
[a b c] xs]
(print "array ")
(print a) (print " ")
(print b) (print " ")
(print c) (println ""))
;; & rest is the tail as a slice, which is an ordinary (slice xs n (len xs))
;; over a local — nothing new, and nothing that outlives the array.
(let [xs [1 2 3 4 5]
[head & tail] xs]
(print "rest ")
(print head) (print " ")
(print (len tail)) (print " ")
(print (at tail 0)) (print " ")
(print (at tail 3)) (println ""))
;; The tail may be empty: naming every element and then asking for the rest
;; is a zero-length slice, not an error.
(let [xs [9 8]
[p q & rest] xs]
(print "empty-tail ")
(print (+ p q)) (print " ")
(print (len rest)) (println ""))
;; Patterns nest through each other: a struct inside an array.
(let [ps [(Point {:x 1 :y 2}) (Point {:x 3 :y 4})]
[{:keys [x]} {y :y}] ps]
(show2 "nested-in-array" x y))
;; A tail of something wider than a machine word. The corpus slices arrays of
;; i32, u8 and f32 and nothing else, so this is the one place the desugared
;; (slice xs n (len xs)) has to get a struct's stride right rather than a
;; scalar's.
(let [ps [(Point {:x 1 :y 2}) (Point {:x 3 :y 4}) (Point {:x 5 :y 6})]
[first & others] ps]
(print "struct-tail ")
(print (.x first)) (print " ")
(print (len others)) (print " ")
(print (.y (at others 0))) (print " ")
(print (.x (at others 1))) (println ""))
;; Evaluate-once. Two patterns, two calls, four names — one call per pattern.
;; Without the temporary each of the four names would call it again: 4, not 2.
(let [{:keys [x y]} (make-point)
{a :x b :y} (make-point)]
(print "calls ")
(print calls) (print " ")
(print (+ x (+ y (+ a b))))
(println ""))
0)