最近发现了一个lisp编程的坑:
Emacs提供了一个alist-get
函数,然而,并咩有一个对应的alist-put
,在自己写了一个例子之后,我发现这个函数的实现有点尴尬:为了保证对alist的引用得到同步的更新,我们只能把新元素插到链表头的后面而不是直接push
到前面。因此,如果做成如下的一个函数,它做不到对nil
的引用进行更新。
(defun alist-put (key alist elem)
"Ensure (KEY . ELEM) exists in ALIST, ALIST must be non-nil."
(if (null alist)
(error "ALIST must be non-nil!")
(let ((cell (assoc key alist)))
(if (null cell)
(let ((newcell (cons key elem))
(head (car alist))
(rem (cdr alist)))
(setcdr newcell rem)
(setcdr head newcell))
(setcdr cell elem)))))
因此只能做成一个宏,这样接口类型就不统一了。
(defmacro alist-put-1 (key alist elem)
"Ensure (KEY . ELEM) exists in ALIST."
`(if (null ,alist)
(set ,alist (list (cons ,key ,elem)))
(let ((cell (assoc ,key ,alist)))
(if (null cell)
(push (cons ,key ,elem) ,alist)
(setcdr cell ,elem)))))
我记得某个教程里说过,alist应该是“如果你想不清楚为什么在这里用就别用的”的那一类东西。如果我们需要用alist去储存配置,应该限制对它的访问方式。比如放在一个slot里面,data frame外面还套了一层,这样就不会导致出现更新引用的问题。把alist直接绑定到某个全局符号下面是坏的实践。