loopの:doと:while (:until) の話

comp.lang.lispのとある記事を見てて気づいたんですが、
(loop :until ... :do ...)
なんて書き方ができたんですね。
私は今まで
(loop :do ... :until)
のようなコードしか見たことがありませんでした。
当然、:untilだけでなく:whileでも同じようなことが出来るみたいです。

で、これらは同じ意味かと思ったら違うみたいです。

(loop :do (print 'oh!) :until t)
; OH!
; => NIL

(loop :until t :do (print 'oh!))
; => NIL

(macroexpand '(loop :do (print 'oh!) :until t))
; =>
; (MACROLET ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-ERROR)))
;  (BLOCK NIL
;   (LET NIL
;    (MACROLET ((LOOP-FINISH NIL '(GO SYSTEM::END-LOOP)))
;     (TAGBODY SYSTEM::BEGIN-LOOP
;      (PROGN (PROGN (PRINT 'OH!)) (WHEN T (LOOP-FINISH)))
;      (GO SYSTEM::BEGIN-LOOP) SYSTEM::END-LOOP
;      (MACROLET
;       ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-WARN)
;         '(GO SYSTEM::END-LOOP))))))))) ;
; T

(macroexpand '(loop :until t :do (print 'oh!)))
; =>
; (MACROLET ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-ERROR)))
;  (BLOCK NIL
;   (LET NIL
;    (MACROLET ((LOOP-FINISH NIL '(GO SYSTEM::END-LOOP)))
;     (TAGBODY SYSTEM::BEGIN-LOOP
;      (PROGN (WHEN T (LOOP-FINISH)) (PROGN (PRINT 'OH!)))
;      (GO SYSTEM::BEGIN-LOOP) SYSTEM::END-LOOP
;      (MACROLET
;       ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-WARN)
;         '(GO SYSTEM::END-LOOP))))))))) ;
; T

:doを先に書いた場合は、その内容が必ず1度は評価されるのに対し、
:whileや:untilを先に書いた場合はテストが先に行われるため、
:doの内容が評価されない場合もあるみたいです。

CLHSを眺めたところ、loopは基本的に書かれたとおりの順番に動くみたいです。
次のように気持ち悪いことも出来ます。

(loop :for i :from 0 :do (print i) :while (< i 3)  :collect i)
; 0
; 1
; 2
; 3
; => (0 1 2)

:whileを:collectより先に書いてあるので、リストに3は含まれません。
もちろん、:whileと:collectの順番を逆にすると、リストに3が含まれます。
loop恐るべし。

Leave a Reply