do

La boucle fondamentale en Common Lisp s'appelle do. C'est une macro assez compliquée avec plusieurs parties facultatives. La syntaxe générale du do est :

(do (varclause-1
     varclause-2
     ...
     varclause-m)
    (terminaison)
  expression-1
  expression-2
  ...
  expression-n)

Chaque varclause détermine une variable d'itération. La forme générale d'une varclause est :

(var init iter)

var est le nom d'une variable, init est sa valeur initiale et iter est une expression à évaluer après chaque itération pour mettre à jour la valeur de var.

La partie iter est facultative. Si elle est absente, la variable ne sera pas modifiée à chaque itération. La partie init est, elle aussi, facultative. En son absence, la variable sera initialisée à nil. Si init est absente, alors iter doit être absente aussi. Dans ce cas, la clause peut-être remplacée par la variable seule.

La partie terminaison a la forme générale suivante :

(test finexpr-1 finexpr-2 ... finexpr-k)

Ici, seul le test est obligatoire. Cette expression sera évaluée avant chaque itération pour déterminer si la boucle doit se terminer. Si c'est le cas, la valeur de l'expression do entière sera la valeur de la dernière des expressions finexpr (progn implicite). S'il n'y a pas de finexpr, la valeur de l'expression do entière sera nil. Exemples (de McCLIM) :

(do ((i 0 (+ i 2)))
    ((= i (length sequence))
     (nreverse res))
  (push (make-point (aref sequence i) (aref sequence (+ i 1))) res))

Ici, il n'y a qu'une variable de boucle i dont la valeur initiale est 0 et qui sera incrémentée de 2 à chaque itération. Lorsque i aura la valeur de l'expression (length sequence), la boucle se terminera avec comme valeur la valeur de l'expression (nreverse res).

(do ((q ks (cddr q)))
    ((null q))
  (let ((k1 (max 0d0 (min 1d0 (car q))))
        (k2 (max 0d0 (min 1d0 (cadr q)))))
    (when (/= k1 k2)
      (push k1 res)
      (push k2 res))))

Dans cet exemple, il y a aussi une seule variable de boucle, initialisée à la valeur de la variable ks, qui est probablement une liste. À chaque itération, on avance de deux éléments dans la liste (avec cddr) et on termine quand q est vide.

Lorsque plusieurs variables d'itération sont présentes, do marche comme let dans le sens où les parties init et iter sont toutes évaluées avant que la première variable ne soit affectée. Exemple :

* (do ((i 0 (1+ i))
       (j 0 i))
      ((> i 5))
    (format t "i: ~a  j: ~a~%" i j))
i: 0  j: 0
i: 1  j: 0
i: 2  j: 1
i: 3  j: 2
i: 4  j: 3
i: 5  j: 4
NIL
*

Ici, j reçoit l'ancienne valeur de i, car la partie iter de la variable j (c'est-à-dire l'expression i) est évaluée avant que la variable i ne soit affectée à sa nouvelle valeur (c'est-à-dire (1+ i)).

Irene DURAND
2011-10-14