flan/test/programs/recur.flan

109 lines
4.0 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 (length 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
;; The bindings are sequential, as a let's are: the second initialiser reads
;; the first name. Only the initial values are; recur still rebinds at once.
(print (loop [a 1 b (+ a 10)]
(if (> a 3) b (recur (+ a 1) (+ b a)))))
(println "") ; 11+1+2+3 = 17
0)