一个奇怪的 Elisp 问题:纯函数两次返回值不一致?

代码:

(defun test ()
  (let ((l '(1)))
    (push 2 l)
    (nreverse l)))

之后,对 (test) 求值两次,第一次返回 '(1 2),第二次返回 '(2 1 2)

试着做了一些实验:

  • 放一个 message 在里面:

    (defun test ()
      (let ((l '(1)))
        (message "l is %s" l)
        (push 2 l)
        (nreverse l)))
    

    可知第一次求值时,l'(1);第二次就是 '(1 2) 了。

  • (let ((l '(1))) 换成 (let ((l (list 1))),好了。

  • 拿掉 nreverse,好了。

Emacs 版本是 26.3。这是为什么呢?

这个主题正好回复了你的问题

3赞

懂了。谢谢!

结果上看类似全局变量,或者 C 的静态变量。quoted object 不只是列表,其它类似字符串、数组、哈希表?都有这个现象,数字跟 nil 应该没问题

(defun foo ()
  (let ((s "A"))
    (cl-incf (aref s 0))
    s))
;; => foo

(foo)
;; => "B"

(foo)
;; => "C"

(foo)
;; => "D"

nreverse为啥设计成这样,是有特殊作用,还是设计错误

n means non-constructive

至于为啥这样实现,这就是单链表就地翻转的算法阿

1赞

可是这个lettest里面,每次let不是应该生成一个全新的l吗……

也就是开发过程中,quote 或者 backquote 都是静态变量。如果想要声明一般变量需要显式调用 (list 1 2 3) 或者 (vector 1 2 3)。我一直以为 '(1 2 3) 和 (list 1 2 3) 是等价的,这可能对 elisp 并不适用

带插值的backquote会被翻译成list+append,所以不是静态的。不带插值的backquote会直接翻译成quote

2赞

同疑问,let 声明的变量难道是一直记录在这个作用域内,不是每次重新生成的 :thinking:

看了之前主题的两个链接,感觉就是 quote object 初始化返回的是个指针地址,后面在同一作用域调用时,还是这个地址,只是指向的东西被改的面目全非了,不知道理解的对不对

1赞

当你把 nreverse 用于 quote object 时,函数就不纯了。

文档有说:

quote returns the argument, without evaluating it. (quote x) yields x .

Warning : quote does not construct its return value, but just returns the value that was pre-constructed by the Lisp reader (see info node Printed Representation).

This means that (a . b) is not identical to (cons 'a 'b) : the former does not cons. Quoting should be reserved for constants that will never be modified by side-effects, unless you like self-modifying code.


所以,你的函数相当于:

``` elisp
(defvar gl '(1))

(defun test ()
  (let ((l gl))
    (push 2 l)
    (nreverse l)))
2赞