Циклы
Введение
Один из самых ценных и одновременно наименее документированных операторов в стандарте Коммон Лисп. Ценен он тем, что позволяет короче и яснее использовать функции высшего порядка (mapping) и рекурсивные операции. Также он позволяет использовать в Лиспе конструкции, более удобные для тех, кто привык работать с более традиционными языками.
Макрос Loop отличается от других операторов Лиспа, и больше напоминает конструкции Си или Паскаля. То есть, для чтения loop-выражений вам потребуется мыслить не только на языке Лиспа, но и на Паскале.
Loop-выражение может содержать выражения, определяющие итерируемые переменные; выражения, завершающие цикл (условно или безусловно); выражения, определяющие последовательность действий на каждой итерации; действия после завершения последней итерации. Кроме того, loop-выражения, как и полагается функциональной парадигме, возвращает какое-то значение. Редко когда приходится использовать все эти возможности, но оных позволяет их эффективно комбинировать.
Код, представленный в оригинале этого руководства, взят из Tutorial for the Common Lisp Loop Macro и использовался там по разрешению Peter Karp. 1)
Примеры
Итерация по списку, печатаем каждый элемент:
(loop for x in '(a b c d e)
do (print x))
A
B
C
D
E
NIL
Итерация параллельно по двум спискам, собираем результат в ассоциированный список:
(loop for x in '(a b c d e)
for y in '(1 2 3 4 5)
collect (cons x y))
((A . 1) (B . 2) (C . 3) (D . 4) (E . 5))
Итерация с использованием счетчика. Возвращает список из элементов, полученных в результате каждой итерации. 2)
(loop for x from1 to 5
for y = (* x 2)
collect y)
(2 4 6 8 10)
Итерация с проходом по списку, параллельно с итерацией по счетчику. Окончание итерации по счетчику не определяется длиной списка. В теле loop-выражения используется условный оператор:
(loop for x in '(a b c d e)
for y from 1
when (> y 1)
do (format t ", ")
do (format t "~A" x))
A, B, C, D, E
NIL
То же самое, но через if:
(loop for x in '(a b c d e)
for y from 1
if (> y 1)
do (format t ", ~A" x)
else do (format t "~A" x))
A, B, C, D, E
NIL
Пример условно-досрочного завершения цикла:
(loop for x in '(a b c d e 1 2 3 4)
until (numberp x)
collect (list x 'foo))
((A FOO) (B FOO) (C FOO) (D FOO) (E FOO))
Также для организации условного завершения может использоваться "while":
(loop for x from 1
for y = (* x 10)
while (< y 100)
do (print (* x 5))
collect y)
5
10
15
20
25
30
35
40
45
(10 20 30 40 50 60 70 80 90)
Loop-выражения могут быть вложены друг в друга:
(loop for x from 1 to 10
collect (loop for y from 1 to x
collect y))
((1) (1 2) (1 2 3) (1 2 3 4) (1 2 3 4 5) (1 2 3 4 5 6) (1 2 3 4 5 6 7)
(1 2 3 4 5 6 7 8) (1 2 3 4 5 6 7 8 9) (1 2 3 4 5 6 7 8 9 10))
На месте итерируемой переменной может стоять несколько переменных в виде списка. 3)
(loop for (a b) in '((x 1) (y 2) (z 3))
collect (list b a))
((1 X) (2 Y) (3 Z))
Ключевое слово "return" (возврат) обеспечивает немедленный выход из цикла и (по желанию) возвращает значение, даваемое ему в виде первого аргумента:
(let ((s "alpha45"))
(loop for i from 0 below (length s)
for ch = (char s i)
when (find ch "0123456789" :test #'eql)
return ch))
#\4
Также есть рад ключевых слов, позволяющих сократить запись loop-выражения с использованием when/return:
(loop for x in '(foo 2)
thereis (numberp x))
T
(loop for x in '(foo 2)
never (numberp x))
NIL
(loop for x in '(foo 2)
always (numberp x))
NIL
Хотите еще?
Тут есть еще некоторое количество примеров, посвященных различным применениям loop. Если вы захотите ознакомится с первоисточником, то загляните в HyperSpec и CLTL.
Библиотеки
Iterate - еще одна возможность организовывать циклы, если вас не устраивает loop и другие стандартные возможности Common Lisp.