5

这些都有效:

(defun testcaseexpr (thecase)
  (case thecase
    ('foo (format t "matched foo"))
    (bar (format t "matched bar"))
    ((funk) (format t "matched funky"))))

这三种表达方式中的哪一种被认为是惯用方式?也许作为一个侧面,为什么它们都在工作,而显然它们的语法不同。事实上,在其他情况下,它们具有完全不同的语义。列表(funk)当然与引用的原子不同,'foo. 然而,只是传递的话foo barfunk所有的工作都是一样的。

4

2 回答 2

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"))))

其中,第一种情况和第三种情况具有相同的结构;子句的部分是对象列表。

也许这个问题是题外话,因为它要求的是“最好的”,而这可能主要是基于意见的。我同意wvxvw 的回答中提出的观点,但我倾向于几乎完全使用您在第三种情况下展示的风格。我有几个原因:

这是最一般的形式。

这是最一般的形式。在 的文档中case,我们读到 annormal-clause ::= (keys form*) keys是键列表的指示符。这意味着子句 like(2 (print 'two))等价于((2) (print 'two)). 通过使用列表而不是非列表,您永远不会丢失任何东西,但是如果您有一些包含多个对象的子句和一些包含单个对象的子句,那么您将拥有所有它们的一致语法。例如,你可以有

(case operator
  ((and or) ...)
  ((if iff) ...)
  ((not)    ...))

更难搞砸了。

这使得混淆 和 的特殊情况变得更加t困难otherwise。该文档说明了以下(强调添加):

键——对象列表的指示符。在 case 的情况下,符号totherwise不得用作键指示符。要将这些符号本身称为键,必须分别使用指示符(t)和 。(otherwise)

在实践中,一些实现将允许您在普通子句中使用totherwise作为,即使看起来不应该允许这样做。例如,在 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)))

使用显式列表可以消除您正在尝试做的任何歧义。尽管totherwise被特别指出,keys是一个列表指示符,这意味着nil(一个原子和一个列表)需要一些特殊的考虑。下面的代码会产生aorb吗?(你能在不测试或检查规范的情况下判断吗?这种情况实际上在示例中突出显示。)

(case nil
  (nil 'a)
  (otherwise 'b))

它返回b。要返回a,第一个正常子句必须是((nil) 'a)

结论

如果您始终确保是一个列表,您将:

  1. 最终得到更一致的代码;
  2. 避免边缘情况错误(特别是如果您正在编写扩展为 的宏case);和
  3. 让你的意图更清楚。
于 2013-11-14T16:48:34.347 回答
0

第二 :)

除非您意外地将宏扩展为类似的东西,否则永远不会使用第一个,当您有多个匹配符号时使用第三个(失败案例)。

于 2013-11-14T12:34:12.760 回答