Common Lisp 如何推送到返回列表?
Common Lisp how to push to a returned list?
假设我有一个列表变量 *test*
设置为 (:v1 (1) :v2 (2))
然后,经过一些字符串配对后,我需要添加另一个 1
到 :v1
,相当于:
(push 1 (getf *test* :v1))
但是,它看起来更像:
(push 1 (getf-string-equal *test* "v1"))
getf-string-equal 在哪里(取自here)
(defun getf-string-equal (plist indicator)
(loop
for (i v) on plist by #'cddr
when (string-equal i indicator)
return v))
但是,我的问题是我无法在返回的列表中使用 setf
。通过副作用,我可以使用一些丑陋的技巧来推动函数内部,但我正在努力避免这种情况。
如何修改通过搜索 属性 作为字符串获得的列表 属性?相当于:
(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
调用来获取一些临时变量。
接下来,我们生成代码来运行您在访问器中使用的相同循环。这里的区别在于,我们不是 returning 实际值,而是 return 包含它的 cons 单元格。这样,我们可以在该单元格中设置 car
并产生实际修改数据结构的效果。如果找到匹配项,我们就这样做,然后就完成了。
但是,如果找不到匹配项,就会出现问题。如果没有这样的 cons
单元格,我们不可能设置它的 car
。这是if
中的第二个案例。我们想简单地扩展为 `(setf ,plist ,value)
,它会有正确的语义。但是,defsetf
不会让我们像宏那样访问实际使用的变量。因此,要完全解决这个问题,包括找不到值的极端情况,我们必须深入研究 define-setf-expander
,defsetf
的完全一般形式。为了让您体验这个宏的通用性,使用它正确编写 setf
扩展,您的扩展必须是一个表达式,其中 returns 五个不同的值.
假设我有一个列表变量 *test*
设置为 (:v1 (1) :v2 (2))
然后,经过一些字符串配对后,我需要添加另一个 1
到 :v1
,相当于:
(push 1 (getf *test* :v1))
但是,它看起来更像:
(push 1 (getf-string-equal *test* "v1"))
getf-string-equal 在哪里(取自here)
(defun getf-string-equal (plist indicator)
(loop
for (i v) on plist by #'cddr
when (string-equal i indicator)
return v))
但是,我的问题是我无法在返回的列表中使用 setf
。通过副作用,我可以使用一些丑陋的技巧来推动函数内部,但我正在努力避免这种情况。
如何修改通过搜索 属性 作为字符串获得的列表 属性?相当于:
(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
调用来获取一些临时变量。
接下来,我们生成代码来运行您在访问器中使用的相同循环。这里的区别在于,我们不是 returning 实际值,而是 return 包含它的 cons 单元格。这样,我们可以在该单元格中设置 car
并产生实际修改数据结构的效果。如果找到匹配项,我们就这样做,然后就完成了。
但是,如果找不到匹配项,就会出现问题。如果没有这样的 cons
单元格,我们不可能设置它的 car
。这是if
中的第二个案例。我们想简单地扩展为 `(setf ,plist ,value)
,它会有正确的语义。但是,defsetf
不会让我们像宏那样访问实际使用的变量。因此,要完全解决这个问题,包括找不到值的极端情况,我们必须深入研究 define-setf-expander
,defsetf
的完全一般形式。为了让您体验这个宏的通用性,使用它正确编写 setf
扩展,您的扩展必须是一个表达式,其中 returns 五个不同的值.