add-to-list和push应该如何选择?

对于configuration (这里不考虑诸如spacemacs这种已经是个distribution的配置) 来说,使用add-to-listpush的区别是什么?以及该如何选择?

比如添加东西到auto-mode-alist里的时候, 应该用哪个?

发这个贴是因为add-to-list的doctoring的一句话:

his is handy to add some elements to configuration variables, but please do not abuse it in Elisp code, where you are usually better off using ‘push’ or ‘cl-pushnew’.

1 个赞

对于像 load-path 跟你提到的 auto-mode-alist 这样的全局变量,add-to-list 很好用。和 push 相比,add-to-list 的优势在于:

  • 能够避免重复添加元素;
  • 能够添加到 List 的末尾。

Emacs Lisp 代码中不应该滥用 add-to-list,我认为因为没什么必要,同理,能用 eq 解决的就没必要用 equal。此外,add-to-list 不适用于 Lexical 语境:

;; -*- lexical-binding: t; -*-

(let ((l '(a b c)))
  ;; 有问题
  (add-to-list 'l 'd)
  l)

;; 没问题
(add-to-list 'load-path "~/lisp")
4 个赞

add-to-list 简化版(里边的语句并非杜撰,都是从 add-to-list 原样搬来的,push 同):

#+BEGIN_SRC emacs-lisp :results value pp
(defun add-to-list-lite (list-var element)
  (if (member element (symbol-value list-var))
      list-var
    (set list-var
         (cons element (symbol-value list-var)))))

(let ((l '(a b)))
  (add-to-list-lite 'l 'c)
  l)
#+END_SRC

#+RESULTS:
: (c a b)

简化版 push:

#+BEGIN_SRC emacs-lisp :results value pp
(defmacro push-lite (newelt place)
  (list 'setq place
        (list 'cons newelt place)))

(let ((l '(a b)))
  (push-lite 'c l)
  l)
#+END_SRC

#+RESULTS:
: (c a b)

如果不考虑函数与宏的差别,它俩之间的差异可进一步简化为:

add-to-list  ==>  member + push
2 个赞

add-to-list 主要用于添加列表而不用添加重复元素, 这是和 push 最大的区别

3 个赞

push只能加到list开头,add-to-list可以加到末尾,要iteratively地构造一个list,可以push ... nreverseadd-to-liststackoverflow上有个答案说前者更好,我没看明白,大家怎么看?

因为add-to-list的实现比较野蛮,等效于(set symbol (append (symbol-value symbol) (list new-elt)))

这样如果在循环里为列表添加元素,append会把他第一个参数复制一次产生一个新列表,list包装new-elt也会产生一个新列表,就相当低效率。push等效(setq a (cons new-elt a)),这样虽然也是副作用语义,但是不会破坏列表内部的结构,只是改变引用。

另外add-to-list用symbol-value获取值,如果局部变量遮盖了全局变量,symbol-value还是会获取全局变量的值,有时候会造成难以理解的bug。

1 个赞

凡事要自己实践啊

   (if append
        (append (symbol-value list-var) (list element))
     (cons element (symbol-value list-var) ))
(setq data '(2 3 4))

(let ((data '(5 6 7)))
  (print (add-to-list 'data 8)))

symbol-value is a built-in function in ‘C source code’.

(symbol-value SYMBOL)

Return SYMBOL’s value.  Error if that is void. 
Note that if ‘lexical-binding’ is in effect, this returns the
global value outside of any lexical scope.

不用 lexical-binding就没这种情况了

This is handy to add some elements to configuration variables, but please do not abuse it in Elisp code, where you are usually better off using ‘push’ or ‘cl-pushnew’.

我认为只要用add-to-list就是abused, 所以关键代码不要用:

  • 慢,编译时慢,运行时慢;

  • 语意含糊: push ahead or push back;

这里还提到configuration variables, 其实更改configuration variables要的就是这个destructive operations,期望的结果就是要有副作用的修改。

所以可以尝试这样:

(defmacro push! (newelt seq &optional append uniquely)
  "Push NEWELT to the ahead or back of SEQ.

If optional APPEND is non-nil then push back else push ahead.
If optional UNIQUELY is non-nil then push uniquely."
  (let ((n1 (gensym*))
        (s1 (gensym*)))
    `(let* ((,n1 ,newelt)
            (,s1 (if ,uniquely (delete ,n1 ,seq) ,seq)))
       (set (quote ,seq) (if ,append
                             (append ,s1 (list ,n1))
                           (cons ,n1 ,s1))))))