根据你的定义,很简单:
(defsetf kth-elt kth-upd)
您现在可以使用kth-elt 和(setf kth-elt),而不是使用kth-upd。
例如:
(let ((list (copy-list '(a b c d e f))))
(setf (kth-elt list 3) nil)
list)
=> (A B C NIL E F)
但始终使用SETF 的真正好处是您可以将此设置器与其他设置器结合使用。只需考虑增加一个值:
(let ((list (make-list 10 :initial-element 0)))
(incf (kth-elt list 3))
(incf (kth-elt list 5) 20)
list)
=> (0 0 0 1 0 20 0 0 0 0)
另请参阅 Rainer Joswig 的 this answer,了解有关 places 和 SETF 的更多背景信息。
Setf 扩展器
请注意,您要进行两次列表遍历:首先获取当前值,然后计算新值;只有这样,您才存储新值,从列表的开头开始:
0: (KTH-ELT (0 0 0 0 0 0 0 0 0 0) 3)
1: (KTH-ELT (0 0 0 0 0 0 0 0 0) 2)
2: (KTH-ELT (0 0 0 0 0 0 0 0) 1)
3: (KTH-ELT (0 0 0 0 0 0 0) 0)
3: KTH-ELT returned 0
2: KTH-ELT returned 0
1: KTH-ELT returned 0
0: KTH-ELT returned 0
0: (KTH-UPD (0 0 0 0 0 0 0 0 0 0) 3 1)
1: (KTH-UPD (0 0 0 0 0 0 0 0 0) 2 1)
2: (KTH-UPD (0 0 0 0 0 0 0 0) 1 1)
3: (KTH-UPD (0 0 0 0 0 0 0) 0 1)
3: KTH-UPD returned 1
2: KTH-UPD returned 1
1: KTH-UPD returned 1
0: KTH-UPD returned 1
这也可以通过宏展开看到:
(incf (kth-elt list 3))
... 被宏扩展为:
(LET* ((#:LIST796 LIST) (#:NEW1 (+ 1 (KTH-ELT #:LIST796 3))))
(KTH-UPD #:LIST796 3 #:NEW1))
另一种可能的方法是使用DEFINE-SETF-EXPANDER:
(define-setf-expander kth (list index)
(alexandria:with-gensyms (store cell)
(values `(,cell)
`((nthcdr ,index ,list))
`(,store)
`(setf (car ,cell) ,store)
`(car ,cell))))
该函数返回 5 个不同的代码部分,可以组装这些部分来访问和修改一个地方。 cell 和 store 是使用 GENSYM 引入的局部变量。
变量cell(即以绑定到cell的新符号命名的变量)将绑定到(nthcdr index list)。 store 包含要在该位置设置的值。在这里,它将使用(setf (car cell) store) 放置在适当的位置。此外,该位置的现有值为(car cell)。如您所见,我们需要在底层操纵我们变异的 cons 单元格(当然,空列表会引发错误)。
(incf (kth list 3)) 的宏扩展为:
(LET* ((#:CELL798 (NTHCDR 3 LIST)) (#:STORE797 (+ 1 (CAR #:CELL798))))
(SETF (CAR #:CELL798) #:STORE797))
setter 函数知道如何访问保存我们要更改的值的位置,并且可以直接更改它,这比仅仅一对读取器/写入器函数更有效。
关于可变性的备注
SETF 是围绕可变数据设计的。如果您为网络上的键/值存储编写访问器,以便(remote host key) 将连接并检索一个值,然后(setf (remote host key) value) 将新值发送回,则不能保证远程值总是更新(remote host key) 用作中间位置。
例如,如果值是一个列表,(push val (remote host key)) 将推送在您的主机上创建的本地列表,setf 没有义务实际确保在它是一部分时将结果发送回网络的更大的表达。这允许 SETF 通过改变位置来提高效率,而代价是要求您更加明确。在前面的示例中,您必须直接写入(setf (remote host key) new-list)(而不是作为嵌套位置)才能有效地将新数据发回。