These all work:
(defun testcaseexpr (thecase)
(case thecase
('foo (format t "matched foo"))
(bar (format t "matched bar"))
((funk) (format t "matched funky"))))
Which of these three expressions is considered the idiomatic way? And perhaps as a side point, why are they all working, when clearly they are not the same syntax. In fact in other contexts they have different semantics completely. A list (funk)
is certainly not the same as a quoted atom, 'foo
. Yet just passing in the words foo
bar
and funk
all work the same.
First, note that you've actually only got two cases here. 'foo
is expanded by the reader as (quote foo)
, so your code is equivalent to
(defun testcaseexpr (thecase)
(case thecase
((quote foo) (format t "matched foo"))
(bar (format t "matched bar"))
((funk) (format t "matched funky"))))
wherein the first and third cases have the same structure; the keys part of the clause is a list of objects.
Perhaps this question is off-topic, since it's asking for the “best”, and that might be primarily opinion based. I agree with the points made in wvxvw's answer, but I tend to use the style you've shown in the third case almost exclusively. I've got a couple reasons for this:
It's the most general form.
It's the most general form. In the documentation for case
, we read that in an normal-clause ::= (keys form*)
keys
is a designator for a list of keys. This means that a clause like (2 (print 'two))
is equivalent to ((2) (print 'two))
. You never lose anything by using a list instead of a non-list, but if you have some clauses with multiple objects and some with single objects, you'll have consistent syntax for all of them. E.g., you can have
(case operator
((and or) ...)
((if iff) ...)
((not) ...))
It's harder to mess up.
It makes it harder to mess up the special cases of t
and otherwise
. The documentation says about keys that (emphasis added):
keys—a designator for a list of objects. In the case of case, the
symbols t
and otherwise
may not be used as the keys designator. To
refer to these symbols by themselves as keys, the designators (t)
and
(otherwise)
, respectively, must be used instead.
In practice, some implementations will let you use t
and otherwise
as keys in normal-clauses, even though it seems like this shouldn't be allowed. E.g., in SBCL:
CL-USER> (macroexpand-1 '(case keyform
(otherwise 'a)
(otherwise 'b)))
(LET ((#:G962 KEYFORM))
(DECLARE (IGNORABLE #:G962))
(COND ((EQL #:G962 'OTHERWISE) NIL 'A)
(T NIL 'B)))
Using explicit lists removes any ambiguity about what you're trying to do. Even though t
and otherwise
are called out specifically, keys is a list designator, which means that nil
(an atom and a list) needs some special consideration. Will the following code produce a
or b
? (Can you tell without testing it or checking the spec? This case is actually highlighted in the examples.)
(case nil
(nil 'a)
(otherwise 'b))
It returns b
. To return a
, the first normal-clause would have to be ((nil) 'a)
.
Conclusion
If you always make sure that keys is a list, you'll:
- end up with more consistent looking code;
- avoid edge-case bugs (especially if you're writing macros that expand into
case
); and
- make your intentions clearer.
Second :)
First is never used, unless you expand a macro into something like it by accident, and third is used when you have more then one matching symbol (a fall-through case).