A dotimes whose every iteration continues still counts to its trip count. The existing case fails by hanging if the latch is wrong; this one fails by counting wrong, which is the off-by-one the four-block layout could have. A restart-case clause was made a barrier on reasoning alone and nothing observed it. Now something does. And say what a labelled continue means, which is the half that is not obvious: it advances the named loop's counter and skips the rest of its body, not just the rest of the innermost one.
95 lines
3.5 KiB
Plaintext
95 lines
3.5 KiB
Plaintext
;;;; break and continue, with loop labels.
|
|
;;;;
|
|
;;;; The two things worth asserting here rather than in a unit test, because
|
|
;;;; they are about the code that comes out and not about the checker:
|
|
;;;;
|
|
;;;; 1. A (continue) in a dotimes still advances the counter. The step is the
|
|
;;;; loop's *latch* and not the last form of the body — folded onto the body
|
|
;;;; it would be jumped over and the program would hang, which is a test
|
|
;;;; that fails by never finishing rather than by printing the wrong thing.
|
|
;;;; The watchdog is what turns that back into a failure.
|
|
;;;;
|
|
;;;; 2. A labelled break leaves the loop it names and no other.
|
|
|
|
(defstruct Hit [n i32])
|
|
|
|
(defn main [] i32
|
|
;; break, unlabelled: the innermost loop.
|
|
(let [i 0]
|
|
(while (< i 100)
|
|
(set i (+ i 1))
|
|
(when (= i 4) (break)))
|
|
(print i) (println "")) ; 4
|
|
|
|
;; continue in a while. The advance is written before it, because a while
|
|
;; has no latch of its own — that is the loop's own business and not the
|
|
;; compiler's.
|
|
(let [j 0 seen 0]
|
|
(while (< j 6)
|
|
(set j (+ j 1))
|
|
(when (= (% j 2) 0) (continue))
|
|
(set seen (+ seen j)))
|
|
(print seen) (println "")) ; 1 + 3 + 5 = 9
|
|
|
|
;; continue in a dotimes, which is the one the latch exists for: the counter
|
|
;; must advance on the skipped iteration too, or this never returns.
|
|
(let [sum 0]
|
|
(dotimes [k 5]
|
|
(when (= k 2) (continue))
|
|
(set sum (+ sum k)))
|
|
(print sum) (println "")) ; 0 + 1 + 3 + 4 = 8
|
|
|
|
;; Every iteration continues, and the count is still the trip count: the
|
|
;; latch is on the continue path and not merely reachable from the body. The
|
|
;; case above would hang if it were not; this one would count wrong.
|
|
(let [c 0]
|
|
(dotimes [m 3]
|
|
(set c (+ c 1))
|
|
(continue))
|
|
(print c) (println "")) ; 3
|
|
|
|
;; A labelled break leaves the named loop. Without the label it would leave
|
|
;; the inner one and the outer would run all three times: 0 1 0 1 0 1.
|
|
(dotimes :outer [a 3]
|
|
(dotimes [b 3]
|
|
(when (= b 2) (break :outer))
|
|
(print b) (println ""))) ; 0 1
|
|
|
|
;; A labelled continue starts the *outer* loop's next iteration, so the rest
|
|
;; of the outer body is skipped as well as the rest of the inner one.
|
|
(dotimes :rows [r 3]
|
|
(dotimes [c 3]
|
|
(when (= c 1) (continue :rows))
|
|
(print c) (println ""))
|
|
(println "tail")) ; 0 0 0, and no tail
|
|
|
|
;; until takes a label on the same rule, being a while with a negated test.
|
|
(let [n 0]
|
|
(until :count (> n 10)
|
|
(set n (+ n 1))
|
|
(when (= n 3) (break :count)))
|
|
(print n) (println "")) ; 3
|
|
|
|
;; A loop wholly inside a restart-case body has a perfectly good local
|
|
;; break: nothing the restart-case established is crossed by leaving a loop
|
|
;; that is inside it. This is the case the blanket refusal on return would
|
|
;; have caught and the relative rule does not.
|
|
(let [t 0]
|
|
(restart-case
|
|
(while true
|
|
(set t (+ t 1))
|
|
(when (> t 5) (break)))
|
|
(carry-on [] (println "not reached")))
|
|
(print t) (println "")) ; 6
|
|
|
|
;; The same the other way round: a handler-bind written inside the loop body
|
|
;; is entered and left before the break runs, so the break crosses nothing.
|
|
(let [u 0]
|
|
(while true
|
|
(handler-bind [(Hit [c] (println "hit"))]
|
|
(signal (Hit {.n 1})))
|
|
(set u (+ u 1))
|
|
(when (= u 2) (break)))
|
|
(print u) (println "")) ; hit hit 2
|
|
0)
|