Lisp 对于case,哪种方法是表达case的最佳方法?

Lisp 对于case,哪种方法是表达case的最佳方法?,lisp,common-lisp,Lisp,Common Lisp,这些都是有效的: (defun testcaseexpr (thecase) (case thecase ('foo (format t "matched foo")) (bar (format t "matched bar")) ((funk) (format t "matched funky")))) 这三种表达方式中哪一种被认为是惯用的?也许作为一个补充,当它们显然不是相同的语法时,为什么它们都在工作呢。事实上,在其他上下文中,它们的语义完全不同。列表(fun

这些都是有效的:

(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
都是一样的


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

首先,请注意,这里实际上只有两个大小写
'foo
由读者扩展为
(quote foo)
,因此您的代码相当于

(defun testcaseexpr (thecase)
  (case thecase
    ((quote foo) (format t "matched foo"))
    (bar         (format t "matched bar"))
    ((funk)      (format t "matched funky"))))
其中,第一和第三壳体具有相同的结构;该子句的键部分是对象列表

也许这个问题离题了,因为它要求的是“最好的”,而这可能主要是基于观点的。我同意中提出的观点,但我倾向于使用您在第三个案例中展示的样式,几乎完全是这样。我有几个原因:

这是最普遍的形式。 这是最普遍的形式。在
案例
的文档中,我们看到在
普通子句中::=(键形式*)
是键列表的指示符。这意味着像
(2(print'two))
这样的子句相当于
((2)(print'two))
。通过使用列表而不是非列表,您永远不会丢失任何东西,但是如果您有一些包含多个对象的子句和一些包含单个对象的子句,那么所有这些子句的语法都是一致的。例如,你可以

(case operator
  ((and or) ...)
  ((if iff) ...)
  ((not)    ...))
更难搞砸。 这使得处理
t
的特殊情况变得更加困难。文档中介绍了以下关键点(添加了重点):

键—对象列表的指示符在这种情况下 符号
t
否则
不能用作键指示器。
至 将这些符号本身称为键、指示符
(t)
和 必须分别使用
(否则)

实际上,有些实现允许您使用
t
作为普通子句中的键,尽管这似乎是不允许的。例如,在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
否则
,但键是一个列表指示符,这意味着
nil
(原子和列表)需要特别考虑。以下代码是否会生成
a
b
?(在不测试或检查规范的情况下,您能说出来吗?示例中实际突出显示了这种情况。)

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

结论 如果您始终确保密钥是一个列表,您将:

  • 最终得到外观更一致的代码
  • 避免边缘大小写错误(尤其是在编写扩展到
    大小写的宏时);及
  • 让你的意图更清楚

  • 第一个是符号列表(QUOTE-FOO),所以如果案例是QUOTE,它将匹配。通常不是你想要的,但有时是——你永远不想用‘来写它以避免混淆。即使如此,在像<code>(case x(t'foo)(否则为‘bar’)这样的情况下会发生什么?
    。SBCL将其扩展为
    (COND((EQL#:G956'T)NIL'FOO)(T NIL'BAR))
    。看起来
    t
    实际上是作为一个符号进行比较的。这就是为什么我通常更喜欢第三个选项。@Xach我对第一个选项所说的话具有讽刺意味,暗示这很可能是一个错误(可能是一个疏忽),而不是有人想故意放在他们的程序中。第三个选项不仅仅是为了“多个匹配符号”。符号
    t
    否则
    nil
    都需要它。它不用于不合格案例;漏检案例由
    t
    标记,否则
    @JoshuaTaylor用于漏检案例。失败案例是一个,其中许多案例中只有一个需要匹配。您所指的被称为
    默认值
    case.>(testcaseexpr'quote)->匹配foo@RainerJoswig这很有趣,为什么会这样foo=(quote foo)->quote匹配quote。引用case子句项是没有意义的。
    (case nil
      (nil 'a)
      (otherwise 'b))