26.23 Дополнительные возможности

The Loop Facility provides the named construct to name a loop so that the Common Lisp special operator return-from can be used.

Для задания имени цикла используется конструкция named. Данное имя впоследствии можно использовать в return-from.

The loop keywords initially and finally designate loop constructs that cause expressions to be evaluated before and after the loop body, respectively.

Символы initially и finally обозначают выражения, которые будут выполнены перед и после тела цикла соответственно.

The code for any initially clauses is collected into one progn in the order in which the clauses appeared in the loop. The collected code is executed once in the loop prologue after any implicit variable initializations.

Выражения после всех initially собираются в один progn в исходном порядке. Сгруппированный код выполняется единожды перед началом итераций.

The code for any finally clauses is collected into one progn in the order in which the clauses appeared in the loop. The collected code is executed once in the loop epilogue before any implicit values are returned from the accumulation clauses. Explicit returns in the loop body, however, will exit the loop without executing the epilogue code.

Выражения после всех finally собираются в один progn в исходном порядке. Сгруппированный код выполняется единожды после выполнения всех итераций в эпилоге перед неявным возвратом значений. В случае явного выхода из цикла, эпилог не выполняется.

26.23.1 Data Types

26.23.2 Типы данных

Many loop constructs take a type-spec argument that allows you to specify certain data types for loop variables. While it is not necessary to specify a data type for any variable, by doing so you ensure that the variable has a correctly typed initial value. The type declaration is made available to the compiler for more efficient loop expansion. In some implementations, fixnum and float declarations are especially useful; the compiler notices them and emits more efficient code.

Многие конструкции принимают аргумент type-spec, который позволяет задать тип для переменной. Конечно в этом нет прямой необходимости, но декларации типов упрощают дальнейшую работу с программой. Декларация типов также помогает компилятору оптимизировать программу. Особенно это касается типов fixnum и float.

The type-spec argument has the following syntax:

type-spec ::= of-type d-type-spec
d-type-spec ::= type-specifier | (d-type-spec . d-type-spec)

A type-specifier in this syntax can be any Common Lisp type specifier. The d-type-spec argument is used for destructuring, as described in section 26.23.3. If the d-type-spec argument consists solely of the types fixnum, float, t, or nil, the of-type keyword is optional. The of-type construct is optional in these cases to provide backward compatibility; thus the following two expressions are the same:
;;; This expression uses the old syntax for type specifiers.
(loop for i fixnum upfrom 3 ...)

;;; This expression uses the new syntax for type specifiers.
(loop for i of-type fixnum upfrom 3 ...)

Аргумент type-spec выглядит так:

type-spec ::= of-type d-type-spec
d-type-spec ::= type-specifier | (d-type-spec . d-type-spec)

На месте type-specifier может быть любой спецификатор типа. Аргумент d-type-spec используется для деструктуризации, как написано в разделе 26.23.3. Если аргумент d-type-spec состоит только из ключевых слов fixnum, float, t или nil, символ of-type необязателен. Это оставлено для обратной совместимости.

;;; Старый стиль кода.
(loop for i fixnum upfrom 3 ...)

;;; Новый стиль кода.
(loop for i of-type fixnum upfrom 3 ...)

26.23.3 Destructuring

Destructuring allows you to bind a set of variables to a corresponding set of values anywhere that you can normally bind a value to a single variable. During loop expansion, each variable in the variable list is matched with the values in the values list. If there are more variables in the variable list than there are values in the values list, the remaining variables are given a value of nil. If there are more values than variables listed, the extra values are discarded.

Suppose you want to assign values from a list to the variables a, b, and c. You could use one for clause to bind the variable numlist to the car of the specified expression, and then you could use another for clause to bind the variables a, b, and c sequentially.

;;; Collect values by using FOR constructs.
(loop for numlist in ’((1 2 4.0) (5 6 8.3) (8 9 10.4))
      for a integer = (first numlist)
      and for b integer = (second numlist)
      and for c float = (third numlist)
      collect (list c b a))
    ((4.0 2 1) (8.3 6 5) (10.4 9 8))

Destructuring makes this process easier by allowing the variables to be bound in parallel in each loop iteration. You can declare data types by using a list of type-spec arguments. If all the types are the same, you can use a shorthand destructuring syntax, as the second example following illustrates.

;;; Destructuring simplifies the process.
(loop for (a b c) (integer integer float) in
      ’((1 2 4.0) (5 6 8.3) (8 9 10.4))
      collect (list c b a)))
    ((4.0 2 1) (8.3 6 5) (10.4 9 8))

;;; If all the types are the same, this way is even simpler.
(loop for (a b c) float in
      ’((1.0 2.0 4.0) (5.0 6.0 8.3) (8.0 9.0 10.4))
      collect (list c b a))
    ((4.0 2.0 1.0) (8.3 6.0 5.0) (10.4 9.0 8.0))

If you use destructuring to declare or initialize a number of groups of variables into types, you can use the loop keyword and to simplify the process further.

;;; Initialize and declare variables in parallel
;;; by using the AND construct.
(loop with (a b) float = ’(1.0 2.0)
      and (c d) integer = ’(3 4)
      and (e f)
      return (list a b c d e f))
    (1.0 2.0 3 4 NIL NIL)

A data type specifier for a destructuring pattern is a tree of type specifiers with the same shape as the tree of variables, with the following exceptions:

;;; Declare X and Y to be of type VECTOR and FIXNUM, respectively.
(loop for (x y) of-type (vector fixnum) in my-list do ...)

If nil is used in a destructuring list, no variable is provided for its place.

(loop for (a nil b) = ’(1 2 3)
      do (return (list a b)))
    (1 3)

Note that nonstandard lists can specify destructuring.

(loop for (x . y) = ’(1 . 2)
      do (return y))
    2

(loop for ((a . b) (c . d))
          of-type ((float . float) (integer . integer))
          in ’(((1.2 . 2.4) (3 . 4)) ((3.4 . 4.6) (5 . 6)))
      collect (list a b c d))
    ((1.2 2.4 3 4) (3.4 4.6 5 6))

[It is worth noting that the destructuring facility of loop predates, and differs in some details from, that of destructuring-bind, an extension that has been provided by many implementors of Common Lisp.—GLS]

[Выражение цикла] initially {expr}*

[Выражение цикла] finally [do | doing] {expr}*

[Выражение цикла] finally return expr

The initially construct causes the specified expression to be evaluated in the loop prologue, which precedes all loop code except for initial settings specified by constructs with, for, or as. The finally construct causes the specified expression to be evaluated in the loop epilogue after normal iteration terminates.

The expr argument can be any non-atomic Common Lisp form.

Clauses such as return, always, never, and thereis can bypass the finally clause.

The Common Lisp macro return (or the return loop construct) can be used after finally to return values from a loop. The evaluation of the return form inside the finally clause takes precedence over returning the accumulation from clauses specified by such keywords as collect, nconc, append, sum, count, maximize, and minimize; the accumulation values for these pre-empted clauses are not returned by the loop if return is used.

The constructs do, initially, and finally are the only loop keywords that take an arbitrary number of (non-atomic) forms and group them as if by using an implicit progn.

Examples:

;;; This example parses a simple printed string representation
;;; from BUFFER (which is itself a string) and returns the
;;; index of the closing double-quote character.

(loop initially (unless (char= (char buffer 0) #\")
                  (loop-finish))
      for i fixnum from 1 below (string-length buffer)
      when (char= (char buffer i) #\")
        return i)

;;; The FINALLY clause prints the last value of I.
;;; The collected value is returned.

(loop for i from 1 to 10
      when (> i 5)
        collect i
      finally (print i)) ;Prints 1 line
11
    (6 7 8 9 10)

;;; Return both the count of collected numbers
;;; as well as the numbers themselves.

(loop for i from 1 to 10
      when (> i 5)
        collect i into number-list
        and count i into number-count
      finally (return (values number-count number-list)))
    5 and (6 7 8 9 10)


[Выражение цикла] named name

The named construct allows you to assign a name to a loop construct so that you can use the Common Lisp special operator return-from to exit the named loop.

Only one name may be assigned per loop; the specified name becomes the name of the implicit block for the loop.

If used, the named construct must be the first clause in the loop expression, coming right after the word loop.

Example:

;;; Just name and return.
(loop named max
      for i from 1 to 10
      do (print i)
      do (return-from max ’done)) ;Prints 1 line
1
    DONE