when et unless

Parfois, une conditionnelle peut être utilisée pour évaluer une expression (ou plusieurs) dans le cas où la condition vaut vrai (ou faux) et de ne rien faire dans le cas contraire. Common Lisp a deux macros : when et unless pour ce type de situation.

Les deux acceptent un nombre d'arguments arbitraire (au moins un) dont le premier est la condition et les autres des expressions qui seront évaluées séquentiellement si la condition vaut vrai (pour when) ou faux (pour unless) (progn implicite).

Voici quelques exemples tirés de la bibliothèque McCLIM :

(when (port-lookup-mirror port sheet)
  (xlib:destroy-window (port-lookup-mirror port sheet))
  (port-unregister-mirror port sheet (sheet-mirror sheet)))

(when (null (port-lookup-mirror port pixmap))
  (let* ((window (sheet-direct-mirror (pixmap-sheet pixmap)))
         (pix (xlib:create-pixmap
                :width (round (pixmap-width pixmap))
                :height (round (pixmap-height pixmap))
                :depth (xlib:drawable-depth window)
                :drawable window)))
      (port-register-mirror port pixmap pix))
  (values))

(unless (and (eq align-x :left) (eq align-y :baseline))            
  (setq tx (- tx (ecase align-x
                   (:left 0)
                   (:center (round text-width 2))
                   (:right text-width))))
  (setq ty (ecase align-y
             (:top (+ ty baseline))
             (:center (+ ty baseline (- (floor text-height 2))))
             (:baseline ty)
             (:bottom (+ ty baseline (- text-height))))))

(unless active-p
  (setf active-p t)
  (note-gadget-activated (gadget-client gadget) gadget))

On peut se demander pourquoi when et unless existent et pourquoi il n'est pas aussi acceptable d'écrire :

(if (port-lookup-mirror port sheet)
    (progn (xlib:destroy-window (port-lookup-mirror port sheet))
           (port-unregister-mirror port sheet (sheet-mirror sheet))))

que

(when (port-lookup-mirror port sheet)
  (xlib:destroy-window (port-lookup-mirror port sheet))
  (port-unregister-mirror port sheet (sheet-mirror sheet)))

La réponse est à la fois simple et surprenante pour un débutant. En Common Lisp la lisibilité du code dépend crucialement de l'indentation des lignes de code. Il est donc important de respecter cette indentation. Mais avec un grand nombre d'expressions imbriquées, cette indentation nécessite souvent un nombre considérable de caractères blancs au début de chaque ligne. Ces caractères blancs ne contiennent aucune information et peuvent créer des lignes de texte très longues, d'où la nécessité parfois de les couper. Davantage de lignes implique moins de code visible à l'écran à un moment donné. L'indentation, cruciale pour la lisibilité du code, représente une ressource limitée qu'il convient d'économiser le plus possible.

Les deux expressions (xlib:destroy...) et (port-unregister...) dans l'exemple ci-dessus sont indentées de 11 positions dans l'exemple avec if, mais seulement 2 positions dans l'exemple avec when, ce qui représente une économie considérable d'indentation.

Mise à part l'indentation, when et unless sont préférables, car pour cette situation, ce sont les constructions les plus spécifiques possible. C'est un principe général de style que de toujours choisir la construction la plus spécifique possible pour une situation donnée.

Irene DURAND
2011-10-14