为什么这个全局变量只能设置两次,不能再设置了?

Why is this global variable only able to be set twice, and no more?

我试图在 elisp 中制作一个简单的解析器,我遇到了一个问题,我 defvar 一个全局的,然后我 setq 一个新的值进入它。这是第一次。但是,后来 setq 每次都失败。

下面的代码是问题的简化:

(defvar buf '(BUF))
(defvar head nil)

(defun pparse (seq)

  (defun status ()
    (princ (format "Parse: %40s || %-20s\n"
           (prin1-to-string seq) (prin1-to-string buf))))

  (while seq
    (status)
    (setq head (car seq))
    (setq seq (cdr seq))
    (cond ((equal "x" head)
       (nconc buf (list head)))
      ((equal "," head)
       (setq buf '(BUF))
       ;;(setcdr buf nil) <- fixes it but doesn't answer my question
      )))

  (status))

(pparse '("x" "," "x" "," "x" "," "x"))

产生此输出:

Parse:            ("x" "," "x" "," "x" "," "x") || (BUF)               
Parse:                ("," "x" "," "x" "," "x") || (BUF "x")           
Parse:                    ("x" "," "x" "," "x") || (BUF)               
Parse:                        ("," "x" "," "x") || (BUF "x")           
Parse:                            ("x" "," "x") || (BUF "x")           
Parse:                                ("," "x") || (BUF "x" "x")       
Parse:                                    ("x") || (BUF "x" "x")       
Parse:                                      nil || (BUF "x" "x" "x")   

如您所见,第二列被剪裁了一次,但随后每次都增长。

如果您取消注释 setcdr 行,这将按预期工作(输出如下)。您甚至可以转储 setq。我明白为什么这会修复它,但不明白为什么最初的错误会首先发生。

Parse:            ("x" "," "x" "," "x" "," "x") || (BUF)               
Parse:                ("," "x" "," "x" "," "x") || (BUF "x")           
Parse:                    ("x" "," "x" "," "x") || (BUF)               
Parse:                        ("," "x" "," "x") || (BUF "x")           
Parse:                            ("x" "," "x") || (BUF)               
Parse:                                ("," "x") || (BUF "x")           
Parse:                                    ("x") || (BUF)               
Parse:                                      nil || (BUF "x")           

顺便说一句,即使我关闭词法范围,行为也是一样的。

您不能改变文字数据,例如 '(BUF),并期望得到合理的结果。在您的代码中,nconc 是一个变异操作。

就 "why" 而言,您所看到的行为是由于 (setq buf '(BUF)) 表达式造成的。每次都将它设置为 same 对象,因为它是一个字面数据——你不应该用 nconc 之类的东西来改变它。如果你改成(setq buf (list 'BUF)),那么每次都会生成一个新对象,你可以放心nconc that.

这是因为数据标识在 Elisp 中的工作方式。如果您将 nconc 替换为 append,您将不会遇到这些问题。我强烈建议您总体上远离 nconc(以及像 setcdr 这样的朋友),尤其是在您不太熟悉 Elisp 的情况下。