这些都有效:
(defun testcaseexpr (thecase)
(case thecase
('foo (format t "matched foo"))
(bar (format t "matched bar"))
((funk) (format t "matched funky"))))
这三种表达中的哪一种被认为是惯用的方式?也许作为一个侧面点,为什么它们都在工作,显然它们的语法不一样。实际上在其他情况下,它们完全具有不同的语义。列表(funk)
肯定与引用的原子'foo
不同。然而,只是传递foo
bar
和funk
这两个词都是一样的。
答案 0 :(得分:7)
首先,请注意你实际上只有两个案例。读者将'foo
扩展为(quote foo)
,因此您的代码等同于
(defun testcaseexpr (thecase)
(case thecase
((quote foo) (format t "matched foo"))
(bar (format t "matched bar"))
((funk) (format t "matched funky"))))
其中第一和第三种情况具有相同的结构;该子句的 keys 部分是一个对象列表。
也许这个问题是偏离主题的,因为它要求“最好”,而这可能主要是基于意见的。我同意wvxvw's answer中提出的观点,但我倾向于使用你在第三种情况下几乎完全展示的风格。我有几个理由:
这是最常见的形式。在case
的文档中,我们读到normal-clause ::= (keys form*)
keys
中有一个键列表的指示符。这意味着像(2 (print 'two))
这样的子句等同于((2) (print 'two))
。你永远不会通过使用列表而不是非列表来丢失任何东西,但是如果你有一些带有多个对象的子句和一些带有单个对象的子句,你将拥有所有这些对象的一致语法。例如,你可以拥有
(case operator
((and or) ...)
((if iff) ...)
((not) ...))
这使得t
和otherwise
的特殊情况变得更加困难。文档说明了 keys (强调添加):
keys - 对象列表的指示符。 在案件的情况下, 符号
t
和otherwise
不得用作键指示符。 To 将这些符号本身称为键,指示符(t)
和 分别必须使用(otherwise)
。
在实践中,某些实现可以让您在普通子句中使用t
和otherwise
作为键,即使看起来不应该允许这样做。例如,在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)))
使用显式列表可以消除您尝试执行的操作的任何歧义。即使专门调出t
和otherwise
, keys 也是列表指示符,这意味着nil
(原子和列表)需要特别考虑。以下代码会生成a
还是b
? (您可以在不测试或检查规范的情况下判断吗?这个案例实际上在示例中突出显示。)
(case nil
(nil 'a)
(otherwise 'b))
返回b
。要返回a
,第一个normal-clause必须是((nil) 'a)
。
如果您始终确保键是一个列表,您将:
case
的宏);和答案 1 :(得分:0)
第二个:)
首先从不使用,除非你意外地将宏扩展成类似的东西,并且当你有一个匹配符号(一个落空案例)时使用第三个。