setf 是如何工作的?
How do setf works under the hood?
目前正在学习 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)
表达式是 returning 'b
,因此 setf
实际上与 (setf b bb)
一起工作。然后我不明白 setf
如何推断它必须修改哪个对象(这里是列表 *x*
),而 elt
的 return 值同时表明它来自一个集合,以及一个指向该集合的指针。
好像很复杂。
2。 setf 是一个宏
这个想法是,由于 setf
是一个宏,它直接与 (setf (elt *x* 1) bb)
一起工作,因此可以提取 elt *x* 1
部分来推断使用了哪个 object/collection ,因此必须修改。
它似乎不是很有效,也不可靠,也不耐复杂操作。但是,由于我无法 运行 此代码:
(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
是一个宏。
您可以阅读有关 Generalized Reference 的所有细节,但基本上,它是这样工作的:
(setf symbol expression)
等同于 setq
否则,我们有(setq (symbol arguments) expression)
。
如果有一个名为(setf symbol)
的函数(是的,你没看错,一个长度为2的列表命名的函数!),那么它就被调用了。
否则“生成setf
扩展”,使用来自的定义
defsetf
or define-setf-expander
.
SETF
是一个将值设置为 位置 的宏。地点表示具有 setf expansion. There are various kinds of places built-in, and you can define more (see for example DEFSETF
and DEFINE-SETF-EXPANDER
, function call forms as places and macro forms as places).
的表格
您可以使用 GET-SETF-EXPANSION
获取表单的 setf 扩展。它returns五个值。例如,
(get-setf-expansion '(elt *x* 1))
;=> (#:*X*660)
; (*X*)
; (#:NEW1)
; (SB-KERNEL:%SETELT #:*X*660 1 #:NEW1)
; (ELT #:*X*660 1)
第五个值是一个 getter 形式,在评估时,returns 该位置的当前值。第四个是 setter 形式,在评估时,为该位置设置一个新值。在这里你可以看到 SBCL 使用 SB-KERNEL:%SETELT
来设置值。
第一个值是一个变量名列表,在计算 setter/getter 表单时,这些变量名应该绑定到第二个值中的表单返回的值。第三个值是一个存储变量列表,它应该绑定到 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))))
我们需要做的就是绑定变量,然后计算setter。请注意,应将环境传递给 GET-SETF-EXPANSION
。我们忽略第五个值(getter),因为我们不需要它。 MULTIPLE-VALUE-BIND
用于绑定store变量,因为可能不止一个
(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,正在学习 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)
表达式是 returning 'b
,因此 setf
实际上与 (setf b bb)
一起工作。然后我不明白 setf
如何推断它必须修改哪个对象(这里是列表 *x*
),而 elt
的 return 值同时表明它来自一个集合,以及一个指向该集合的指针。
好像很复杂。
2。 setf 是一个宏
这个想法是,由于 setf
是一个宏,它直接与 (setf (elt *x* 1) bb)
一起工作,因此可以提取 elt *x* 1
部分来推断使用了哪个 object/collection ,因此必须修改。
它似乎不是很有效,也不可靠,也不耐复杂操作。但是,由于我无法 运行 此代码:
(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
是一个宏。
您可以阅读有关 Generalized Reference 的所有细节,但基本上,它是这样工作的:
(setf symbol expression)
等同于 setq
否则,我们有(setq (symbol arguments) expression)
。
如果有一个名为(setf symbol)
的函数(是的,你没看错,一个长度为2的列表命名的函数!),那么它就被调用了。
否则“生成setf
扩展”,使用来自的定义
defsetf
or define-setf-expander
.
SETF
是一个将值设置为 位置 的宏。地点表示具有 setf expansion. There are various kinds of places built-in, and you can define more (see for example DEFSETF
and DEFINE-SETF-EXPANDER
, function call forms as places and macro forms as places).
您可以使用 GET-SETF-EXPANSION
获取表单的 setf 扩展。它returns五个值。例如,
(get-setf-expansion '(elt *x* 1))
;=> (#:*X*660)
; (*X*)
; (#:NEW1)
; (SB-KERNEL:%SETELT #:*X*660 1 #:NEW1)
; (ELT #:*X*660 1)
第五个值是一个 getter 形式,在评估时,returns 该位置的当前值。第四个是 setter 形式,在评估时,为该位置设置一个新值。在这里你可以看到 SBCL 使用 SB-KERNEL:%SETELT
来设置值。
第一个值是一个变量名列表,在计算 setter/getter 表单时,这些变量名应该绑定到第二个值中的表单返回的值。第三个值是一个存储变量列表,它应该绑定到 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))))
我们需要做的就是绑定变量,然后计算setter。请注意,应将环境传递给 GET-SETF-EXPANSION
。我们忽略第五个值(getter),因为我们不需要它。 MULTIPLE-VALUE-BIND
用于绑定store变量,因为可能不止一个
(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)