loop-finish
loop-finish Local Macro
Syntax:
loop-finish ⟨no arguments⟩ →
Description:
The loop-finish macro can be used lexically within an extended loop form to terminate that form “normally.” That is, it transfers control to the loop epilogue of the lexically innermost extended loop form. This permits execution of any finally clause (for effect) and the return of any accumulated result.
Examples:
;; Terminate the loop, but return the accumulated count.
(loop for i in ’(1 2 3 stop-here 4 5 6)
when (symbolp i) do (loop-finish)
count i)
→ 3
;; The preceding loop is equivalent to:
(loop for i in ’(1 2 3 stop-here 4 5 6)
until (symbolp i)
count i)
→ 3
;; While LOOP-FINISH can be used can be used in a variety of
;; situations it is really most needed in a situation where a need
;; to exit is detected at other than the loop’s ‘top level’
;; (where UNTIL or WHEN often work just as well), or where some
;; computation must occur between the point where a need to exit is
;; detected and the point where the exit actually occurs. For example:
(defun tokenize-sentence (string)
(macrolet ((add-word (wvar svar)
‘(when ,wvar
(push (coerce (nreverse ,wvar) ’string) ,svar)
(setq ,wvar nil))))
**loop-finish**
(loop with word = ’() and sentence = ’() and endpos = nil
for i below (length string)
do (let ((char (aref string i)))
(case char
(#\Space (add-word word sentence))
(#\. (setq endpos (1+ i)) (loop-finish))
(otherwise (push char word))))
finally (add-word word sentence)
(return (values (nreverse sentence) endpos)))))
→ TOKENIZE-SENTENCE
(tokenize-sentence "this is a sentence. this is another sentence.")
→ ("this" "is" "a" "sentence"), 19
(tokenize-sentence "this is a sentence")
→ ("this" "is" "a" "sentence"), NIL
Side Effects:
Transfers control.
Exceptional Situations:
Whether or not loop-finish is fbound in the global environment is implementation-dependent; however, the restrictions on redefinition and shadowing of loop-finish are the same as for symbols in the COMMON-LISP package which are fbound in the global environment. The consequences of attempting to use loop-finish outside of loop are undefined.
See Also:
loop, Section 6.1 (The LOOP Facility)
Notes:
Expanded Reference: loop-finish
Basic usage — terminate and return accumulated value
loop-finish exits the loop "normally," executing any finally clause and returning any accumulated result. Compare this with return, which exits the loop immediately without running finally.
;; loop-finish triggers the finally clause and returns the collected result
(loop for x in '(1 2 3 :stop 4 5)
when (keywordp x) do (loop-finish)
collect x)
=> (1 2 3)
loop-finish vs return
;; With loop-finish: the finally clause runs
(loop for x in '(1 2 3 :stop 4 5)
when (keywordp x) do (loop-finish)
collect x into result
finally (return (cons :finished result)))
=> (:FINISHED 1 2 3)
;; With return: the finally clause does NOT run
(loop for x in '(1 2 3 :stop 4 5)
when (keywordp x) do (return (cons :aborted result))
collect x into result
finally (return (cons :finished result)))
=> (:ABORTED 1 2 3)
Using loop-finish in deeply nested code
loop-finish is most useful when the exit decision happens inside nested code within the loop body, where until or while clauses cannot reach.
;; Process items from a queue, stopping when we hit an error
(loop for item in '((:ok 1) (:ok 2) (:err 3) (:ok 4))
for (status value) = item
when (eq status :err)
do (format t "Error at value ~A, stopping.~%" value)
(loop-finish)
sum value)
.. Error at value 3, stopping.
..
=> 3
loop-finish with a finally clause for cleanup
(loop for line in '("data1" "data2" "" "data3")
when (string= line "")
do (loop-finish)
count t into processed
finally (format t "Processed ~D lines.~%" processed)
(return processed))
.. Processed 2 lines.
..
=> 2