Common lisp Common Lisp如何推送到返回的列表?

Common lisp Common Lisp如何推送到返回的列表?,common-lisp,side-effects,Common Lisp,Side Effects,假设我有一个列表变量*test*设置为(:v1(1):v2(2)) 然后,在一些字符串配对之后,我需要将另一个1添加到:v1,相当于: (push 1 (getf *test* :v1)) (push 1 (getf-string-equal *test* "v1")) 但是,它看起来更像: (push 1 (getf-string-equal *test* "v1")) 其中getf字符串等于(取自) 然而,我的问题是,我不能在返回的列表上使用setf。我可以使用一些难看的技巧来推动函数

假设我有一个列表变量
*test*
设置为
(:v1(1):v2(2))

然后,在一些字符串配对之后,我需要将另一个
1
添加到
:v1
,相当于:

(push 1 (getf *test* :v1))
(push 1 (getf-string-equal *test* "v1"))
但是,它看起来更像:

(push 1 (getf-string-equal *test* "v1"))
其中getf字符串等于(取自)

然而,我的问题是,我不能在返回的列表上使用
setf
。我可以使用一些难看的技巧来推动函数内部,附带副作用,但我正试图避免这种情况

如何修改通过以字符串形式搜索属性而获得的列表属性?相当于:

(push 1 (getf *test* :v1))
(push 1 (getf-string-equal *test* "v1"))
谢谢。

注意:这个答案不是一个完整的解决方案,因为它不能处理找不到钥匙的情况。这只是给你指出正确的方向

这个问题有几个层次。首先,
push
是一个或多或少扩展为
setf
调用的宏。因此,我们需要为
getf string equal
定义一个
setf
扩展。这很容易做到,只需一点复杂

(defsetf getf-string-equal (plist indicator) (value)
  (let ((i (gensym))
        (rest (gensym))
        (match (gensym)))
    `(let ((,match (loop
                      for (,i . ,rest) on ,plist by #'cddr
                      when (string-equal ,i ,indicator)
                      return ,rest)))
       (if ,match
           (setf (car ,match) ,value)
           nil))))
让我们把它分解一下。我们使用长格式的
defsetf
来定义函数的扩展
plist
indicator
是参数,与前面相同,
value
是要分配的新值。但这不是一个函数;它更接近于宏。所以我们要生成一些代码。为此,我们需要一些
gensym
调用来获取一些临时变量

接下来,我们生成运行与访问器中使用的相同循环的代码。这里的区别是,我们将返回包含它的cons单元格,而不是返回实际值。这样,我们就可以在该单元格中设置
car
,并实际修改数据结构。如果找到匹配项,我们就这样做,我们就完蛋了


但是,如果没有找到匹配项,则会出现问题。如果没有这样的
cons
单元格,我们就不可能设置它的
car
。这是本
if
中的第二种情况。我们只想扩展到一个
`(setf,plist,value)
,它将具有正确的语义。但是,
defsetf
不允许我们像宏那样访问实际使用的变量。因此,为了完全解决这个问题,包括找不到值的拐角情况,我们必须深入研究
defsetf
的完全通用形式。为了让您体验一下这个宏的通用性,为了使用它正确地编写一个
setf
扩展,您的扩展必须是一个返回五个不同值的表达式。

为什么不直接使用
(intern(string upcase“v1”)“KEYWORD”)
?因为我不知道我能做到:-)我会试试这个好把戏。虽然我最终在Sylwester评论中使用了这个技巧,但我还是接受了,因为它展示了如何解决问题,这反过来又解释了如何解决一类类似的问题。是的,如果你能避免黑洞,即
setf
扩展,我强烈建议你这样做。我认为,每次我必须进行
setf
扩展时,我必须查找
defsetf
宏,并提醒自己其参数的工作方式。希望这个答案能帮助其他人解决类似的问题