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.
103 lines
3.7 KiB
Plaintext
103 lines
3.7 KiB
Plaintext
;;;; loop and recur.
|
|
;;;;
|
|
;;;; What is worth asserting here rather than in a unit test is the code that
|
|
;;;; comes out, and there are four things:
|
|
;;;;
|
|
;;;; 1. A loop answers with the value of its body — the accumulator comes back
|
|
;;;; without a mutable slot and without a sentinel flag.
|
|
;;;; 2. recur rebinds every name *at once*. A swap is the test that fails if
|
|
;;;; the writes were interleaved with the reads.
|
|
;;;; 3. recur is a jump, not a call. A loop that goes round ten million times
|
|
;;;; would overflow the stack if it were a call, and this one returns.
|
|
;;;; 4. A loop whose body never falls off the end (every path recurs or
|
|
;;;; returns) still terminates, which is the Never-bodied shape.
|
|
|
|
(defn gcd [a i32 b i32] i32
|
|
(loop [x a y b]
|
|
(if (= y 0)
|
|
x
|
|
(recur y (% x y)))))
|
|
|
|
;; The body is Never: neither arm produces a value, so there is no result slot
|
|
;; and no break — nothing falls off the end of this loop.
|
|
(defn first-over [n i32] i32
|
|
(loop [i 0]
|
|
(if (> (* i i) n)
|
|
(return i)
|
|
(recur (+ i 1)))))
|
|
|
|
;; Named so the match below has a return type to read None out of.
|
|
(defn step [i i32] (Option i32)
|
|
(if (= i 4) None (Some i)))
|
|
|
|
(defn main [] i32
|
|
;; The value of the body, with no mutable accumulator anywhere.
|
|
(print (loop [i 0 acc 0]
|
|
(if (= i 5)
|
|
acc
|
|
(recur (+ i 1) (+ acc i)))))
|
|
(println "") ; 0+1+2+3+4 = 10
|
|
|
|
;; Simultaneous rebinding. Interleaved writes would give 1 1.
|
|
(let [p (loop [a 1 b 2 n 0]
|
|
(if (= n 3)
|
|
a
|
|
(recur b a (+ n 1))))]
|
|
(print p) (println "")) ; three swaps: 2
|
|
|
|
(print (gcd 1071 462)) (println "") ; 21
|
|
(print (first-over 50)) (println "") ; 8
|
|
|
|
;; A jump and not a call: ten million frames is not a stack this has.
|
|
(print (loop [i 0]
|
|
(if (= i 10000000) i (recur (+ i 1)))))
|
|
(println "") ; 10000000
|
|
|
|
;; recur in the tail of a let, and of a when inside a do — both are tails,
|
|
;; and both are how a loop actually gets written.
|
|
(print (loop [i 0 acc 1]
|
|
(let [next (* acc 2)]
|
|
(if (= i 6) acc (recur (+ i 1) next)))))
|
|
(println "") ; 2^6 = 64
|
|
|
|
;; A Unit-bodied loop: it is run for its effect and answers with nothing.
|
|
(let [n 0]
|
|
(loop [i 0]
|
|
(when (< i 3)
|
|
(print i)
|
|
(recur (+ i 1))))
|
|
(println "") ; 012
|
|
(print n) (println "")) ; 0
|
|
|
|
;; A while nested inside a loop keeps its own break: the loop is a barrier
|
|
;; only to a jump that would *leave* it.
|
|
(print (loop [i 0 acc 0]
|
|
(if (= i 4)
|
|
acc
|
|
(let [j 0 hit 0]
|
|
(while (< j 10)
|
|
(set j (+ j 1))
|
|
(when (= j 3) (set hit 1) (break)))
|
|
(recur (+ i 1) (+ acc hit))))))
|
|
(println "") ; 4
|
|
|
|
;; A move-only accumulator, carried round by recur and answered with. This
|
|
;; is the shape the form exists for: no mutable local, no sentinel flag, and
|
|
;; the Vec is the loop's value. recur writes every name on the way round, so
|
|
;; the "moves a value bound outside the loop" rule is not about acc.
|
|
(let [v (loop [acc (vec-new i32) i 0]
|
|
(if (= i 3)
|
|
acc
|
|
(do (push acc i) (recur acc (+ i 1)))))]
|
|
(dotimes [i (len v)] (print (at v i)))
|
|
(println "") ; 012
|
|
(free v))
|
|
|
|
;; A match arm is a tail too.
|
|
(print (loop [i 0 acc 0]
|
|
(match (step i)
|
|
(Some v) (recur (+ i 1) (+ acc v))
|
|
None acc)))
|
|
(println "") ; 0+1+2+3 = 6
|
|
0)
|