【问题标题】:Usage of DEFSETFDEFSETF的使用
【发布时间】:2018-02-25 19:47:53
【问题描述】:

从标准描述中很难理解,所以:

例如,我正在尝试将某个列表 (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))))).

还创建了一个函数,用于更新该值。

 (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))))

现在我可以实际使用它,但我想了解它和 DEFSETF 之间的区别。另外我还是不明白。如何“教” defsetf 使用这些。谢谢帮忙。

【问题讨论】:

    标签: lisp common-lisp


    【解决方案1】:

    根据你的定义,很简单:

    (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 个不同的代码部分,可以组装这些部分来访问和修改一个地方。 cellstore 是使用 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)(而不是作为嵌套位置)才能有效地将新数据发回。

    【讨论】:

    • 不幸的是,关于 setf 的整个事情都是最初的问题。尝试这样使用,得到:(defparameter ls '(1 2 3 4 5))(setf (kth-elt ls 4) 11); in: SETF (KTH-ELT LS 4) ; (FUNCALL #'(SETF LISP-TRAINING::KTH-ELT) #:NEW1 #:LS608 4) ; ==&gt; ; (SB-C::%FUNCALL #'(SETF LISP-TRAINING::KTH-ELT) #:NEW1 #:LS608 4) ; ; caught STYLE-WARNING: ; undefined function: (SETF KTH-ELT) ; ; compilation unit finished ; Undefined function: ; (SETF KTH-ELT) ; caught 1 STYLE-WARNING condition
    • @AndrewS。该错误表示它正在尝试调用(setf kth-elt),这意味着您尚未评估(defsetf kth-elt kth-upd)
    • 哦,对不起,我一开始就弄错了。那么我是否必须将(defsetf kth-elt kth-upd) 放在代码中的某个位置,然后它就会被设置?
    • @AndrewS。是的。当您评估defsetf 时,您的实现不知何故得知(setf kth-elt) 实际上是由kth-upd 实现的。在您的错误消息中,您可以看到 #'(setf lisp-training::kth-elt),这是正在查找的函数。 CL 中的函数可以像往常一样用符号命名,也可以使用(setf &lt;symbol&gt;) 列表。但是您的案例中尚未定义该功能。
    【解决方案2】:

    作为 coredump 答案的补充,值得注意的是,以下内容有效,并且在我看来,比使用 defsetf 好得多:

    (defun kth-elt (lst k)
      (cond ((> 0 k) nil)
            ((= 0 k) (car lst))
            ((< 0 k) (kth-elt (cdr lst) (- k 1)))))
    
    (defun (setf kth-elt) (new lst k)
      (cond ((> 0 k) nil)
            ((= 0 k) (setf  (car lst) new))
            ((< 0 k) (setf (kth-elt (cdr lst) (- k 1)) new))))
    

    有些情况下您需要defsetf,但并不常见。

    (当然kth-elt 本身只是elt 的一个特例:在现实生活中你不需要写任何东西。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多