La macro case est similaire à cond. Elle permet de confronter un objet à plusieurs clauses. Les expressions de la clause contenant l'objet seront évaluées.
Le premier paramètre du case est une expression arbitraire qui est toujours évaluée. Sa valeur donne un objet Common Lisp arbitraire (souvent un symbole). Après le premier paramètre figure une suite de clauses. Chaque clause contient une liste d'objets (ou un seul objet) à comparer avec la valeur de l'expression. Si cette valeur figure parmi les objets de la liste, les expressions de la clause seront évaluées (progn implicite). La liste de la dernière clause peut être remplacée par otherwise ou t, indiquant une clause à évaluer en dernier recours. Exemple (de McCLIM) :
(case after
(:start (push new-item menu))
((:end nil) (setf menu (nconc menu (list new-item))))
(:sort (setf menu (sort (cons new-item menu)
#'string-lessp
:key #'command-menu-item-name)))
(t (push new-item
(cdr (member after menu
:key #'command-menu-item-name
:test #'string-equal)))))
Dans cet exemple, l'expression est la variable after. La première clause est sélectionnée si la valeur de cette variable est le symbole (mot-clé) :start. Dans ce cas, l'expression (push new-item menu) sera évaluée. Si ce n'est pas le cas, mais que la valeur est soit :end, soit nil, alors l'expression de la deuxième clause sera évaluée, etc.
Chaque clause commence donc soit par une liste d'objets soit par un seul objet. Pour supprimer tout risque d'ambiguïté, une clause (sauf la dernière) ne peut pas commencer par le seul symbole t ou le seul symbole otherwise. Si l'un de ces symboles doit être utilisé, il faut le mettre dans une liste à un seul élément, donc (t) et (otherwise) respectivement.
Dans une clause, le seul objet nil sera interprété comme la liste vide, et les expressions de la clause ne seront jamais évaluées. Pour choisir l'objet nil, écrire (nil).
Attention, le prédicat utilisé pour déterminer si la valeur de l'expression se trouve dans la liste d'objets d'une clause est eql. Si les objets sont des symboles, des caractères ou des nombres, cela ne pose pas de problème. Pour utiliser d'autres types d'objets commes des listes ou des chaînes de caractères, il faut faire attention ; la méthode normale ne s'applique probablement pas :
* (defparameter abc "abc")
ABC
* (case abc
("fga" 'no)
("abc" 'yes)
(t 'none))
NONE
*
Il y a deux autres versions de case : ecase et ccase. Seule la première sera détaillée ici. La clause t ou otherwise du case normal est facultative. Lorsque elle n'est pas présente, et qu'aucun des objets des autres clauses ne correspond, la valeur de l'expression case est nil. Parfois, on souhaite vérifier qu'au moins un des objets correspond. Dans ce cas, on peut choisir ecase (pour ``error case''). Cette version n'a pas de clause t ou otherwise et dans la situation décrite ci-dessus, au lieu de nil, on obtient une erreur.
On peut se poser la question de pourquoi case existe puisque que cond est plus général. La réponse est que case est potentiellement plus performant. Avec case, une étiquette ne peut pas figurer dans plusieurs clauses, et toutes les étiquettes sont des constantes, connues à la compilation. Le compilateur peut donc par exemple utiliser le hachage parfait pour émettre le code qui permet de sélectionner la bonne clause.