[踩坑] make-list 创建的列表中的元素竟然是同一个对象

又被 emacs 的内置函数坑了一把(上一个是 mapcan: 踩坑分享: 小心 mapcan 的副作用:joy: ,想当然的认为 make-list 创建的元素是不同的对象,改了一个face属性导致所有的元素都变了,找了一晚上的问题 :upside_down_face:,最后发现是这个 make-list 函数只创建一个对象的原因 。list不是一个链表嘛,make-list 自然会被理解为也创建一个相同元素的链表,同一个对象不符合直觉呀,这样设计难道又是为了节省内存?

(pop-buffer-insert 10
  (let ((lst (make-list 3 (concat "emacs"))))
    (setf (car lst)
          (let ((str (car lst)))
            (add-face-text-property
             0 (length str) 'bold t str)
            str))
    (string-join lst " ")))

返回三个粗体的字符串:emacs emacs emacs

解决方法倒是简单,复制一份新的字符串对象就好:

(pop-buffer-insert 10
  (let ((lst (make-list 3 (concat "emacs"))))
    (setf (car lst)
          (let ((str (copy-sequence (car lst))))
            (add-face-text-property
             0 (length str) 'bold t str)
            str))
    (string-join lst " ")))

返回: emacs emacs emacs

Return a newly created list of length LENGTH, with each element being INIT.

docstring 字面意思上就是同一个

来你说一下, 如果不是同一个对象, 你打算怎么提供拷贝方法?

因为make-list是一个primitive function,不是special-form。它不会对element进行延迟求值。

因此,在make-list作用期间,element是一个已经完成求值的表达式,它是不可变的。

对同一常量重复进行引用以节省内存是合理的。

1 个赞