疑问:setf 新增元素为何在函数体内失效呢?

(setq orig '((a . 1)
             (b . 2)))

(setq added '((b . 0)
              (c . 3)
              (d . 4)))
;; 操作 1
(mapc (lambda (x) (setf (alist-get (car x) orig) (cdr x))) added)
orig ;; => ((d . 4) (c . 3) (a . 1) (b . 0))

;; 操作 2
(defmacro alist-set! (orig added)
  `(mapc (lambda (x) (setf (alist-get (car x) ,orig) (cdr x))) ,added))
(alist-set! orig added)
orig ;; => ((d . 4) (c . 3) (a . 1) (b . 0))

;; 操作 3
(defun alist-set (orig added)
   (mapc (lambda (x) (setf (alist-get (car x) orig) (cdr x))) added))
(alist-set orig added)
orig ;; => ((a . 1) (b . 0))

setf会修改orig这个list,而外面的orig跟函数alist-set里面的orig并不是同一个variable。

当key,也就是(car x),在orig里的时候,setf会修改对应的value,由于两个orig里面的cons cells是共用的,所以如你所见,b的value的确变成了0。

然而当key不在orig时,setf会修改orig,新增一个element,也就是(setq orig (cons .. orig))。这就只会修改alist-set里面的orig,而不影响外面的orig

你是说 函数参数传递的是浅拷贝,他们只共享已存在的cons cell,所以替换是可以的,但是新增的话不会算到原变量的头上。那这么说的话,函数内对传递的参数结构体删除、修改都可以成功,但是新增不行。

那我要是直接对传递的参数pop可以成功,push同样会失败,是吗?难怪我换了好几种写法全失败了。

那如果我想就地新增元素的话,改怎么传递参数呢?

(defun alist-set (orig-var added)
  (setq orig (symbol-value orig-var))
  (mapc (lambda (x) (setf (alist-get (car x) orig) (cdr x))) added)
  (set orig-var orig))
(alist-set 'orig added) 

修改了一下,终于可以了,你是直接传递一个symbol,最后直接对symbol重新赋值。我的版本是只传递了一个value,找不到symbol,所以没法新增。

其实新增也行,比方说nconc。删除的话有delq,修改的话有rplaca(setf (nth ...) ...)

重点是那些函数是否destructive,会alter the list。

然而pop不行,pop就只是setq,没有改变list本身。

setf 不算是 destructive 的吗?

setf也可以只用来赋值。

(lambda (x) (setf x ...))(lambda (x) (setf (car x) ...))就有区别。

setq就只是赋值了。

哦哦,多谢。再想请问一个问题, let binding里面的变量该如何理解呢? 比如说:

(setq test '(1 2 3))

;; test 1
(let ((tail (cdr test)))
  (setf tail 4))
test ;;=> (1, 2, 3)

;; test 2
(let ((tail (cdr test)))
  (setf (cdr tail) 4))
test ;;=> (1 2 . 4)
  1. let binding中的 cons cell 是共享的吗?和函数传叁的区别在哪里呢?
  2. 如果1不成功,为何2又突然成功了呢?

一样的,tail是另外一个variable,所以(setf tail ...)不影响test。但(cdr tail)(cdr (cdr test))是同一个place,所以test也会被修改。

所以 1只是赋值,2中是 in-place 操作。好的,多谢指导!

最好不要对 list 进行 setcdr setcar 等破坏性操作,也包括 alist 和 plist 这种本质上还是 list 的结构