There is no TCO here and recur is not a cheaper substitute for one: the compiler verifies the call is in the loop body's tail position, so the mistake is a compile error where it was written rather than a stack overflow somewhere else. A loop is a let, a While whose condition is true, and two jumps — emit.ml is untouched, and the barrier question recur asks is the one labelled break already answered. Tail position is a permission that is withdrawn at the top of check, the same read-and-withdraw defer_ok does, handed back only by a block's last form, both arms of an if and a match arm. So nothing enumerates the forms that are not tails, which a pre-pass over the Ast would have had to, and would have had to keep doing. loop is also a barrier for break and continue, which is added rather than inherited: a loop answers with the value of its body and a jump out has no value to give. That is also why it takes no label. A while inside a loop keeps its own break. Two things the shape forced. A loop binding is a plain name, because destructuring would make recur's argument count unreadable off the binding vector. And in_loop's "moves a value bound outside the loop" rule had to be told about the loop's own names, or (loop [v (vec-new i32)] ...) would have been refused for doing the ordinary thing.
91 lines
3.2 KiB
Plaintext
91 lines
3.2 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 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)
|