Exemple de macro

Une macro Common Lisp est une fonction dont l'objectif n'est pas d'effectuer un calcul à l'exécution, mais de transformer une expression Common Lisp en une autre expression Common Lisp à la compilation ou au chargement.

Prenons l'exemple d'une application de traitement d'image, pour laquelle certains calculs ont besoin d'un grand nombre d'histogrammes. Ces histogrammes prennent beaucoup de place et leur création demande un temps de calcul considérable. Dans l'application, on trouve donc des suites d'instructions de ce type :

(setf (image-histograms image) (compute-histograms image))
calcul-1
...
calcul-n
(setf (imgage-histograms image) nil)

L'idée est d'affecter d'abord un créneau dans l'image avec le résultat du calcul des histogrammes, puis d'effectuer les calculs nécessitant l'existence des histogrammes, puis finalement de mettre nil dans ce créneau afin que le ramasse-miettes puisse récupérer la mémoire occupée par les histogrammes. En fait, cet exemple pose un problème assez grave. Si l'un des calcul-i provoque une erreur, l'instruction qui remet le créneau à nil ne sera pas exécutée. L'espace mémoire occupé par les histogrammes ne sera pas récupéré avant la prochaine affectation du créneau. Pour éviter ce problème, il faut écrire :

(setf (image-histograms image) (compute-histograms image))
(unwind-protect
  (progn
    calcul-1
    ...
    calcul-n)
  (setf (imgage-histograms image) nil))

Ici l'expression unwind-proctect garantit que la dernière affectation sera effectuée, même en cas d'erreur (voir chapitre 17). La suite d'expressions ci-dessus sera sans doute répétée un grand nombre de fois dans l'application. Elle devient alors un idiome de l'application. Chaque fois que l'application a besoin de calculs utilisant les histogrammes de l'image, le programmeur doit entourer ses calculs avec les expressions ci-dessus. Cela pose des problèmes :

C'est une situation similaire à celle qui provoque la décision d'écriture d'une fonction : une suite d'expressions est souvent répétée et pour éviter la répétition physique, on crée une fonction avec un nom qui suggère ce que fait la suite d'expressions. Mais ici, il est impossible d'en faire une fonction. La raison principale est que les instructions à effectuer ne sont pas toujours les mêmes. Ces instructions peuvent même contenir une boucle ou n'importe quel autre calcul arbitrairement compliqué.

Une macro, par contre est capable de capturer cet idiome. Par exemple, une telle macro permettra d'écrire :

(with-image-histograms image
  calcul-1
  ...
  calcul-n)

Ici, l'idiome a été nommé, permettant facilement de ne pas l'oublier et de le reconnaître immédiatement, de la même façon qu'une fonction permet de reconnaître une suite d'instructions à exécuter.

Voici la macro Common Lisp permettant l'utilisation ci-dessus :

(defmacro with-image-histograms (image-var &body body)
  `(progn
     (setf (image-histograms ,image-var)
           (compute-histograms ,image-var))
     (unwind-protect
       (progn
         ,@body)
       (setf (image-histograms ,image-var) nil))))

Le reste de ce chapitre sera consacré à l'explication des divers composants de cette définition.

Irene DURAND
2011-10-14