为什么这个全局变量只能设置两次,不能再设置了?
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 的情况下。
我试图在 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 的情况下。