as-slice was a warning, not an operation. The input type already decides which of the two things happens — a Vec can only be borrowed, an array or a string can only be viewed, and no call site picks between them — so the second name expressed no choice a reader could make. And it warned at the moment the view is taken, which is the one moment nothing is wrong; the danger arrives later, at the push. slice now takes a Vec at all three arities and as-slice is gone. (slice v lo) was free, and is the arity the Vec never had: the runtime already reads a hi of -1 as "to the end", so the tail form passes the caller's lo and the same -1 — no slot, no length read, no second evaluation. The merge is entirely in the checker; the Vec path builds the flan_vec_as_slice call it always built and neither backend has a line about any of it. A Vec a call returned is refused at every arity, and not for the array's reason. (slice (mk)) over an array dangles. (slice (make-vec)) does not — the storage outlives the expression — but the header is a temporary, so nothing can ever free the block. The refusal says that and names the let. The name's own refusal sits in ordinary_call after every table, so a program that defines an as-slice still reaches its own. It reads for somebody who has never heard of the old name and writes the call back out, spelling each argument that is a name or a number. The warning moved to where it bites: BUILT.md gains a section beside the Vec table and the push row points at it, spec-memory.md's Borrowing says the same. Investigated and deliberately not built — a diagnostic for a live view at the push. (reserve v 100) then a slice, a push and a read is correct code under the contract the spec chose, so any flag on it is a false positive by the language's own semantics rather than by an approximation. FIX.org has the finding and the syntactic sketch that does not work.
95 lines
3.6 KiB
Plaintext
95 lines
3.6 KiB
Plaintext
;;;; into — a fused transformation, and not a transducer.
|
|
;;;;
|
|
;;;; What is asserted here is the thing a unit test cannot see: the loop that
|
|
;;;; comes out is one loop, and the values it produces are the ones the chain
|
|
;;;; describes in the order it was written.
|
|
;;;;
|
|
;;;; 1. The order of the transforms is the order of the stages. (filter even?)
|
|
;;;; before (map double) is not the same program as after it, and both are
|
|
;;;; here with different answers.
|
|
;;;; 2. There is no intermediate collection. `pulls` counts every call to the
|
|
;;;; transform functions: one pass over the source is one call per element
|
|
;;;; per stage it reaches, and a chain that built a Vec per stage would pull
|
|
;;;; a different number.
|
|
;;;; 3. A source that is already a name is only read. `src` is a (Vec i32),
|
|
;;;; which is move-only, and it is still alive and freeable afterwards.
|
|
;;;; 4. A source that is a call is evaluated once, not once per element.
|
|
|
|
(defonce pulls i32 0)
|
|
|
|
(defn double [x i32] i32
|
|
(set pulls (+ pulls 1))
|
|
(* x 2))
|
|
|
|
(defn even? [x i32] bool
|
|
(set pulls (+ pulls 1))
|
|
(= (% x 2) 0))
|
|
|
|
(defonce builds i32 0)
|
|
|
|
;; A source that is a call. It must be made once, however many elements come
|
|
;; out of it. It borrows rather than allocating, which is the shape a call in
|
|
;; this position wants: the macro binds the value to a name the caller cannot
|
|
;; see, so an owning temporary here would be a leak nobody can reach.
|
|
(defn source [xs [i32]] [i32]
|
|
(set builds (+ builds 1))
|
|
(slice xs 0 (len xs)))
|
|
|
|
(defn wide [x i32] f32 (f32 x))
|
|
(defn bigf? [x f32] bool (> x 2.5))
|
|
|
|
(defn show [v [i32]] ()
|
|
(dotimes [i (len v)] (print (at v i)) (print " "))
|
|
(println ""))
|
|
|
|
(defn main [] i32
|
|
;; No transforms: a copy into the destination named in the form.
|
|
(let [xs [7 8 9]
|
|
v (into xs (vec-new i32))]
|
|
(show (slice v)) ; 7 8 9
|
|
(free v))
|
|
|
|
;; map then filter.
|
|
(let [xs [1 2 3 4 5 6]
|
|
v (into xs (vec-new i32) (map double) (filter even?))]
|
|
(show (slice v)) ; 2 4 6 8 10 12
|
|
(free v))
|
|
|
|
;; filter then map, over the same source: a different answer, because the
|
|
;; stages are in the order they were written.
|
|
(let [xs [1 2 3 4 5 6]
|
|
v (into xs (vec-new i32) (filter even?) (map double))]
|
|
(show (slice v)) ; 4 8 12
|
|
(free v))
|
|
|
|
;; One pass and no intermediate collection. The two chains above pulled
|
|
;; 6 doubles + 6 evens, then 6 evens + 3 doubles: 21.
|
|
(print pulls) (println "") ; 21
|
|
|
|
;; A name as the source is read, not moved: src is still alive here.
|
|
(let [src (into [3 1 2] (vec-new i32))
|
|
v (into src (vec-new i32) (map double))]
|
|
(show (slice v)) ; 6 2 4
|
|
(show (slice src)) ; 3 1 2
|
|
(free v)
|
|
(free src))
|
|
|
|
;; A type-changing map: the chain's element name is rebound at the new type
|
|
;; by each stage, and the push sees the destination's element type. One name,
|
|
;; shadowed — a let binding's value is checked before its name is bound, so
|
|
;; each stage reads the stage before it.
|
|
(let [xs [1 2 3 4]
|
|
v (into xs (vec-new f32) (map wide) (filter bigf?))]
|
|
(dotimes [i (len v)] (print (at v i)) (print " "))
|
|
(println "") ; 3 4
|
|
(free v))
|
|
|
|
;; A source that is a call is bound once, so it is made once however many
|
|
;; elements come out of it.
|
|
(let [xs [1 2 3 4]
|
|
v (into (source (slice xs 0 4)) (vec-new i32) (filter even?))]
|
|
(show (slice v)) ; 2 4
|
|
(free v))
|
|
(print builds) (println "") ; 1
|
|
0)
|