从标准描述中很难理解,所以:
例如,我试图将某个列表 (ls) 的第 k 个位置设置为特定值。甚至有我自己的功能,可以访问第 k 个 elt。
(defun kth-elt (lst k)
(cond ((> 0 k) nil)
((equal 0 k) (car lst))
((< 0 k) (kth-elt (cdr lst) (- k 1))))).
Run Code Online (Sandbox Code Playgroud)
还创建了一个函数来更新该值。
(defun kth-upd (lst k new)
(cond ((> 0 k) nil)
((equal 0 k) (setf (car lst) new))
((< 0 k) (kth-upd (cdr lst) (- k 1) new))))
Run Code Online (Sandbox Code Playgroud)
现在我实际上可以使用它,但我想了解它和 DEFSETF 之间的区别。另外我还是不明白。如何“教”defsetf 使用这些。谢谢帮助。
根据您的定义,它很简单:
(defsetf kth-elt kth-upd)
Run Code Online (Sandbox Code Playgroud)
kth-upd您现在可以使用kth-eltand来代替使用(setf kth-elt)。例如:
(let ((list (copy-list '(a b c d e f))))
(setf (kth-elt list 3) nil)
list)
=> (A B C NIL E F)
Run Code Online (Sandbox Code Playgroud)
但一致使用的真正好处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)
Run Code Online (Sandbox Code Playgroud)
另请参阅Rainer Joswig 的回答,了解有关地点和 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
Run Code Online (Sandbox Code Playgroud)
这也可以通过宏展开看出:
(incf (kth-elt list 3))
Run Code Online (Sandbox Code Playgroud)
...宏展开为:
(LET* ((#:LIST796 LIST) (#:NEW1 (+ 1 (KTH-ELT #:LIST796 3))))
(KTH-UPD #:LIST796 3 #:NEW1))
Run Code Online (Sandbox Code Playgroud)
另一种可能的方法可能是使用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))))
Run Code Online (Sandbox Code Playgroud)
该函数返回 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))
Run Code Online (Sandbox Code Playgroud)
setter函数知道如何访问保存我们想要更改的值的地方,并且可以直接更改它,这比仅仅一对reader/writer函数更有效。
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)直接写入(而不是作为嵌套位置)才能有效地将新数据发送回。