;;;; 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)