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.
88 lines
3.7 KiB
Plaintext
88 lines
3.7 KiB
Plaintext
;;;; The evidence for lib/js.ml's value-semantics decision.
|
|
;;;;
|
|
;;;; A Flan struct and a fixed array are values: binding one copies it, and
|
|
;;;; emit.ml spends a memcpy at every such site. A JS object assigns by
|
|
;;;; reference, so an object mapping that does nothing aliases where the LLVM
|
|
;;;; build copied — and the divergence is invisible until something mutates a
|
|
;;;; copy, which is what every line below does.
|
|
;;;;
|
|
;;;; test/programs/values.flan already pins the two simplest cases. This goes
|
|
;;;; past them, to the ones a shallow copy would pass and a wrong one would
|
|
;;;; not: a struct inside a struct, a struct returned out of a function, an
|
|
;;;; element read out of an array of structs, and a global.
|
|
;;;;
|
|
;;;; Fable answers the same question the other way for F# structs on its JS
|
|
;;;; backend — it inserts no clone at all, and its Rust backend does — so this
|
|
;;;; is the property that says which of the two this dialect chose.
|
|
|
|
(defstruct Point [x i32 y i32])
|
|
(defstruct Box [lo Point hi Point n i32])
|
|
|
|
(defonce gp Point)
|
|
|
|
;; A parameter is a copy: writing to it must not reach the caller's value.
|
|
(defn bump [p Point] Point
|
|
(set (.x p) (+ (.x p) 100))
|
|
p)
|
|
|
|
;; A returned struct is a copy of whatever it names, not a second name for it.
|
|
(defn origin-of [b Box] Point
|
|
(.lo b))
|
|
|
|
(defn show [p Point] ()
|
|
(print (.x p)) (print " ") (println (.y p)))
|
|
|
|
(defn main [] i32
|
|
;; ── A let binding copies ────────────────────────────────────────
|
|
(let [a (Point {.x 1 .y 2})
|
|
b a]
|
|
(set (.x b) 99)
|
|
(show a) ; 1 2 — a must not have moved
|
|
(show b)) ; 99 2
|
|
|
|
;; ── A parameter copies ──────────────────────────────────────────
|
|
(let [a (Point {.x 1 .y 2})
|
|
c (bump a)]
|
|
(show a) ; 1 2
|
|
(show c)) ; 101 2
|
|
|
|
;; ── A nested struct copies with its container ───────────────────
|
|
(let [bx (Box {.lo (Point {.x 1 .y 1}) .hi (Point {.x 9 .y 9}) .n 3})
|
|
cy bx]
|
|
(set (.x (.lo cy)) 42)
|
|
(set (.n cy) 7)
|
|
(print (.x (.lo bx))) (print " ") (println (.n bx)) ; 1 3
|
|
(print (.x (.lo cy))) (print " ") (println (.n cy))) ; 42 7
|
|
|
|
;; ── A field read is a copy, not a view ──────────────────────────
|
|
(let [bx (Box {.lo (Point {.x 1 .y 1}) .hi (Point {.x 9 .y 9}) .n 3})
|
|
p (origin-of bx)]
|
|
(set (.x p) 55)
|
|
(print (.x (.lo bx))) (print " ") (println (.x p))) ; 1 55
|
|
|
|
;; ── A global copies both ways ───────────────────────────────────
|
|
(set gp (Point {.x 4 .y 5}))
|
|
(let [g gp]
|
|
(set (.x g) 0)
|
|
(show gp) ; 4 5
|
|
(show g)) ; 0 5
|
|
|
|
;; ── A fixed array is a value too ────────────────────────────────
|
|
(let [xs [1 2 3]
|
|
ys xs]
|
|
(set (at ys 0) 77)
|
|
(print (at xs 0)) (print " ") (println (at ys 0))) ; 1 77
|
|
|
|
;; ── An array of structs copies its elements ─────────────────────
|
|
;; An element read is a value too, so mutating what came out of one must
|
|
;; reach neither array. A shallow copy of the outer array would leave both
|
|
;; naming the same element object, and this would print 8 8 8.
|
|
(let [ps [(Point {.x 1 .y 1}) (Point {.x 2 .y 2})]
|
|
qs ps
|
|
e (at qs 0)]
|
|
(set (.x e) 8)
|
|
(print (.x (at ps 0))) (print " ")
|
|
(print (.x (at qs 0))) (print " ")
|
|
(println (.x e))) ; 1 1 8
|
|
0)
|