A type-changing (map f) is the case into's single shadowed element name would break if the shadowing were a trick rather than the language's rule; it is not, because each stage is a fresh slot at its own type, and into.flan now runs an i32 source into a (Vec f32) to say so. A move-only accumulator carried round by recur is the shape BUILT.md pitches the form on and was untested. It works, and recur.flan now carries a Vec three times round and answers with it. block's empty-body arm returned before the loop that distributes the tail, so (do) in a tail position left ctx.tail set for whatever was checked next. Latent rather than live — every consumer sets it immediately before use, and the leaking form is always Unit-typed — but it is one line to close and the invariant is easier to state closed. Also the PORTING.md line listing loop/recur among the things with no customer: it was built, and the half of that finding that still stands is tail calls, which were not.
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.
|
|
|
|
(defvar 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))
|
|
|
|
(defvar 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 (as-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 (as-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 (as-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 (as-slice v)) ; 6 2 4
|
|
(show (as-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 (as-slice v)) ; 2 4
|
|
(free v))
|
|
(print builds) (println "") ; 1
|
|
0)
|