gpt4 book ai didi

common-lisp - setf 如何在幕后工作?

转载 作者:行者123 更新时间:2023-12-01 07:24:12 26 4
gpt4 key购买 nike

目前正在学习 common lisp,遵循 Peter Seibel 的 Practical Common Lisp(我在第 11 章,关于集合),我很难理解 setf在引擎盖后面工作。

考虑到这个表达式:

(setf a 10)

我完全理解解释器如何 (1) 检索名为 a 的变量。 , 和 (2) 将它指向的值更改为 10 .

现在,在特定集合的情况下,例如列表、向量或哈希表,setf 也可用于更改集合包含的值。例如,使用向量:
(defparameter *x* '(a b c d))
(setf (elt *x* 1) bb)

这让我怀疑 setf ,因为它最终会找到非平凡的可访问信息,或制造黑魔法。我看到了多种可能性。

1. setf 是一个函数
(elt *x* 1)表达式返回 'b , 所以 setf几乎与 (setf b bb) 合作.然后我就不明白了 setf可以推断它必须修改哪个对象(这里是列表 *x* ),而没有返回值 elt既包含它来自集合的指示,又包含指向所述集合的指针。
看起来很复杂。

2. setf 是一个宏

这个想法是,如 setf是一个宏,它直接与 (setf (elt *x* 1) bb) 一起使用因此可以提取 elt *x* 1部分来推断使用了哪个对象/集合,因此必须进行修改。

它似乎效率不高,也不可靠,也不能抵抗复杂的操作。但是,由于我无法运行此代码:
(funcall (find-symbol (concatenate 'string "E" "LT")) *x* 1)  ; -> B
(setf (funcall (find-symbol (concatenate 'string "E" "LT")) *x* 1) 'bb) ; -> ERROR : (SETF FUNCALL) is only defined for functions of the form #'symbol

这让我觉得 setf是一个宏,实现了一个非常简单的启发式方法来检索要调用的函数以及所有其他需要的信息。
看起来很复杂。

3. setf是解释的特例

另一种方法可能是解释器本身对 setf 进行不同的处理,处理一些黑魔法以正确实现预期的行为。
看起来很复杂。

4. 关于 lisp 有一些我不知道的地方

可能是真正的答案。我错过了什么 ?

额外的问题:实现方法是否依赖于 lisp 解释器的实现? (或者,更简单地说,关于 setf 实现的通用 lisp 标准到底定义了什么)
我目前正在使用 clisp,但欢迎了解其他实现。

最佳答案

SETF是一个为位置设置值的宏。地点是指具有 setf expansion 的表单。 .有各种kinds of places内置,您可以定义更多(参见例如 DEFSETF DEFINE-SETF-EXPANDER function call forms as placesmacro forms as places )。

您可以使用 GET-SETF-EXPANSION 获得表单的 setf 扩展。 .它返回五个值。例如,

(get-setf-expansion '(elt *x* 1))
;=> (#:*X*660)
; (*X*)
; (#:NEW1)
; (SB-KERNEL:%SETELT #:*X*660 1 #:NEW1)
; (ELT #:*X*660 1)

第五个值是一个 getter 形式,它在计算时返回该地点的当前值。第四个是一个 setter 形式,在评估时,为该位置设置一个新值。在这里可以看到 SBCL 使用了 SB-KERNEL:%SETELT设置值。

第一个值是变量名称列表,在评估 setter/getter 表单时,该列表应绑定(bind)到第二个值中的表单返回的值。第三个值是存储变量的列表,它应该绑定(bind)到要由 setter 存储的新值。

有了这些我们可以定义一个简单的 MY-SETF -宏。
(defmacro my-setf (place values-form &environment env)
(multiple-value-bind (vars vals stores setter)
(get-setf-expansion place env)
`(let* ,(mapcar #'list vars vals)
(multiple-value-bind ,stores ,values-form
,setter))))

我们需要做的就是绑定(bind)变量,并评估setter。注意环境应该传递给 GET-SETF-EXPANSION .我们忽略了第五个值(getter),因为我们不需要它。 MULTIPLE-VALUE-BIND用于绑定(bind)存储变量,因为可能不止一个。
(let ((list (list 1 2 3 4)))
(my-setf (elt list 2) 100)
list)
;=> (1 2 100 4)

(let ((a 10) (b 20) (c 30))
(my-setf (values a b c) (values 100 200 300))
(list a b c))
;=> (100 200 300)

有多种方法可以定义您自己的位置。最简单的方法是使用 DEFSETF或者只是用 DEFUN 定义一个 setf 函数.例如:
(defun eleventh (list)
(nth 10 list))

(defun set-eleventh (list new-val)
(setf (nth 10 list) new-val))

(defsetf eleventh set-eleventh)

(let ((l (list 1 2 3 4 5 6 7 8 9 10 11 12 13)))
(setf (eleventh l) :foo)
l)
;=> (1 2 3 4 5 6 7 8 9 10 :FOO 12 13)

(get-setf-expansion '(eleventh l))
;=> (#:L662)
; (L)
; (#:NEW1)
; (SET-ELEVENTH #:L662 #:NEW1)
; (ELEVENTH #:L662)


(defun twelfth (list)
(nth 11 list))

(defun (setf twelfth) (new-val list)
(setf (nth 11 list) new-val))

(let ((l (list 1 2 3 4 5 6 7 8 9 10 11 12 13)))
(setf (twelfth l) :foo)
l)
;=> (1 2 3 4 5 6 7 8 9 10 11 :FOO 13)

(get-setf-expansion '(twelfth l))
;=> (#:L661)
; (L)
; (#:NEW1)
; (FUNCALL #'(SETF TWELFTH) #:NEW1 #:L661)
; (TWELFTH #:L661)

关于common-lisp - setf 如何在幕后工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44698426/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com