对于configuration (这里不考虑诸如spacemacs这种已经是个distribution的配置) 来说,使用add-to-list
和push
的区别是什么?以及该如何选择?
比如添加东西到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 ... nreverse
或add-to-list
,stackoverflow上有个答案说前者更好 ,我没看明白,大家怎么看?
cireu
2019 年5 月 16 日 09:21
7
因为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.
cireu
2019 年5 月 16 日 12:20
9
不用 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, 所以关键代码不要用:
这里还提到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))))))