loop-finish — 루프를 "정상적으로" 끝내기
loop-finish — 루프를 "정상적으로" 끝내기
루프 도중에 빠져나가야 하는데, return으로 나가면 finally 절이나 누적 결과를 살리기 어려운 경우가 있어요. 예를 들어 토큰을 세다가 문장의 끝(.)을 만나면 멈추되, 그때까지 모은 결과를 돌려줘야 한다면 단순 return으로는 부족하죠. loop-finish는 루프를 정상 종료로 이끌어, finally 절을 실행하고 누적된 결과를 반환하게 해주는 지역 매크로예요.
본문
loop-finish는 확장형 loop 폼 안에서 어휘적으로 사용할 수 있어요. 실행되면 가장 안쪽 확장형 loop 폼의 에필로그로 제어를 옮겨서, 해당 루프를 정상적으로 끝내요.
loop-finish
즉 finally 절이 (효과를 위해) 실행되고, 지금까지 누적된 결과가 반환돼요. 아래는 심볼을 만나면 루프를 끝내되 지금까지 센 개수는 그대로 돌려주는 예제예요.
(loop for i in '(1 2 3 stop-here 4 5 6)
when (symbolp i) do (loop-finish)
count i)
=> 3
;; 위 루프는 아래와 동일해요.
(loop for i in '(1 2 3 stop-here 4 5 6)
until (symbolp i)
count i)
=> 3
loop-finish의 진짜 가치는 탈출해야 한다는 판단이 루프의 "최상위"가 아닌 깊은 곳에서 이뤄지거나, 탈출 지점까지 계산이 이어진 뒤에 실제로 빠져나가야 할 때 드러나요. 예를 들어 문장을 단어로 쪼개는 함수를 보죠.
(defun tokenize-sentence (string)
(macrolet ((add-word (wvar svar)
`(when ,wvar
(push (coerce (nreverse ,wvar) 'string) ,svar)
(setq ,wvar nil))))
(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 "this is a sentence. this is another sentence.")
=> ("this" "is" "a" "sentence"), 19
.을 만나는 지점이 루프 최상위가 아니라 case 깊숙한 곳이에요. 이때 loop-finish로 탈출하되 마지막 단어를 sentence에 넣고 결과를 돌려주는 finally 절까지 자연스럽게 실행되죠.
예외 상황
loop-finish가 전역 환경에서 함수(fbound)로 정의되어 있는지는 구현에 따라 달라요. 다만 재정의·섀도잉에 대한 제약은 COMMON-LISP 패키지의 전역 함수와 같아요. loop 밖에서 loop-finish를 쓰려고 하면 결과가 정의되지 않아요.
함께 보기
loop