Lisp括号问题

8
这段代码来自于书籍《Lisp之国》。第一版是从书中摘录的。当我阅读它时,我认为在第二行的"at-loc-p"之前不需要括号"(",在第三行的loc之后也不需要")"。
(defun person-at (loc pers per-locs)
       (labels ((at-loc-p (pers)
                 (eq (cadr (assoc pers per-locs)) loc)))
         (remove-if-not #'at-loc-p pers)))

但是当我测试这个时,
(defun person-at (loc pers per-locs)
       (labels (at-loc-p (pers)
                 (eq (cadr (assoc pers per-locs)) loc))
         (remove-if-not #'at-loc-p pers)))

它的意思是:

AT-LOC-P 中的必需参数与 lambda 列表不匹配 (CCL::FUNCNAME CCL::LAMBDA-LIST &BODY CCL::LABELS-FUNCTION-BODY)。[类型为 CCL::SIMPLE-PROGRAM-ERROR 的条件]

这是一条错误信息,可能是由于在调用 AT-LOC-P 函数时传递了错误数量或类型的参数导致的。建议检查代码并确保正确传递参数。

你是否将Lisp中改变运算符优先级的括号与定义列表的括号混淆了?例如(2+3)*5和(a,b,c,d)。这意味着函数需要一个列表作为其第二个参数,但如果你去掉括号,它只是一个列表。 - xycf7
3
@xycf7 - 在Lisp中,没有影响括号的运算符优先级,所有操作都是100%无歧义的,因为所有操作都必须完全用括号括起来。 (2 + 3) * 5在Lisp中将被写成 (* (+ 2 3) 5) - tobyodavies
6个回答

11

LABELS 是指

(defun person-at (loc pers per-locs)
  (labels ((at-loc-p (pers)
             (eq (cadr (assoc pers per-locs)) loc)))
    (remove-if-not #'at-loc-p pers)))

该语法的形式为labels ((函数名 参数列表 [[局部声明* | 局部文档]] 局部表达式*)*) 全局声明* 表达式*,因此您需要提供一个本地函数定义列表才能使其工作。

由于这些本地函数定义本身被括在圆括号中,所以您需要向labels传递这种结构的列表:((fun1 (...) ...) (fun2 (...) ...) ...)

不幸的是,堆栈跟踪和错误消息并没有很好地帮助你找到错误,因为消息没有告诉你问题出现在labels上,而且它也不是堆栈跟踪中最顶层的。在我的本地机器上,4:(CCL::NX1-LABELS ...可能是一个提示(调试缓冲区)。

查看Hyperspec中有关labels的文档以了解更多信息。


7
在其他语言,不是Lisp的语言,括号通常用于分组操作符,并且在许多情况下是可选的。但是在Lisp中,括号总是有意义的。可能没有额外或可选的括号。
最常见的情况是,表达式周围的括号表示函数或宏应用
(foo 1)

在这种情况下,表达式开头可能会出现两个括号,例如当表达式的第一个元素是另一个表达式时,该表达式本身被评估为一个函数。例如,想象一个名为make-adder的函数,它接受一个数字并返回另一个部分应用了加法的函数(顺便说一下,这是柯里化的一个例子):
(defun make-adder (number)
   (lambda (another-number) (+ number another-number)))

我们可以这样创建函数变量increment,然后将其应用于变量:
(defvar increment (make-adder 1))
(increment 5)                      ; ==> 6

但我们也可以直接调用它(好的,在Common Lisp中这样做行不通,但在其他称为"Lisp-1"的Lisp中,相同的语法适用,因此我认为在这里提到它是值得的):

((make-adder 1) 5)                 ; ==> 6

在开头加上双括号。当然,两个括号都是必须的。
最后一种情况描述了您的情况,即当语言或用户宏为其目的使用列表时(您还记得Lisp程序本身就是表达式列表吗?)。例如,defun 知道它的第一个参数必须是符号,第二个参数必须是列表。而 labels 宏知道它的第一个参数必须是定义的列表,每个定义本身都是一个列表。这是为了允许用户一次定义多个标签。
(labels ((definition-1) (definition-2) ...) 
   (do-this) (do-that) ...)

因此,您可以看到,每个括号都代表着某些含义,您不能自行删除它们。

3
你通常是正确的99.9%,但我认为在CL中有几种模糊的情况下,括号实际上是可选的。例如,(defstruct s f)(defstruct s (f)) - Ken
2
@Ken:嗯,f周围的括号似乎确实是可选的,但实际上它们定义了一个具有稍微不同行为的宏:(defstruct s f)仅定义了插槽名称,而(defstruct s (f))则定义了插槽名称,可能还包括initform和插槽选项。在每种情况下,括号的数量都是严格定义的。 - ffriend
1
我可以选择将它们放在那里或不放 - 这基本上就是“可选”的定义。是的,该数字严格定义为0或1组括号,这就是使其成为可选项的原因。 :-) 不确定您所说的“可能,initform和slot选项”是什么意思,因为在任一情况下我都没有指定它们。 - Ken

3

我认为括号“(”是不必要的

实际上,它们是必要的。你不能随意添加或删除括号,就像你不能使用符号asdklfjhsbf代替defun并期望它能正常工作一样。你必须给LABELS提供((function-name lambda-list forms) ... ),这就是LABELS的语法;如果你没有遵循它,编译器将会报错。


2

在Lisp中,括号是重要且必不可少的。现在你可能明白为什么Lisp之国音乐视频会这样唱:"我早餐吃括号...午餐没完成就吃括号...它们看起来很有趣,但却具备语义的力量...这使你的程序变得简洁而有力。"很快你也会做关于它们的梦!


2

事实上,在Lisp中,有一些地方括号有点“不自然”。 通常规则是非常一致的:括号开启一个列表,而列表的第一个元素是要使用的函数,其余列表元素作为参数。 这种一致性也适用于大多数宏和特殊形式,使Lisp代码非常统一......但是在某些宏和特殊操作符中,括号具有不同的含义,并用于分组......例如

(dolist (x L) (print x))

在这种情况下,例如第一个括号中的x并不是要传递L作为参数调用的函数。另一个例子是:
(let ((x 10)
      (y 20))
   (print (+ x y)))

在这种情况下,括号只是用于分组,第一个元素 (x 10) 明显不是要应用的函数,它的第一个元素 x 也不是一个函数。 labels 的工作方式与 let 完全相同,显然需要“额外”的括号来分组,以防定义了多个函数。
虽然这些特殊情况确实有点烦人,但它们非常少,在编写一些 Lisp 代码后,您会内化它们,并且在不经意间就能正确地使用它们。
它们仍然存在不对称性,例如使编写正确的代码遍历器变得非常困难,而且通常当您在 Lisp 的“语法区域”中犯错误时,错误消息并不能真正指出错误的位置。
当然,与其他语言相比,这种“复杂性”实际上微不足道(让我们不要开始讨论在 C++ 模板中犯错误时消息有多清晰)。
我认为 Lisp 语法“微不足道”甚至根本不存在并不完全正确。Lisp 语法存在,即使不是在字符级别,而是在 Lisp 表单级别,除了一些特殊形式和宏之外,它几乎是微不足道的。

1

正如@danlei所说,let/labels/flet的第一部分必须是一个函数定义列表。如果只有一个元素,则会出现那些冗余的双括号。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接