Common lisp setf如何在引擎盖下工作?
目前正在学习common lisp,遵循Peter Seibel的实用common lisp(我在第11章,关于集合),我很难理解Common lisp setf如何在引擎盖下工作?,common-lisp,setf,lisp-macros,Common Lisp,Setf,Lisp Macros,目前正在学习common lisp,遵循Peter Seibel的实用common lisp(我在第11章,关于集合),我很难理解setf如何在后台工作 考虑到这一表述: (setf a 10) 我完全理解解释器如何(1)检索名为a的变量,以及(2)将其指向的值更改为10 现在,对于特定集合,例如列表、向量或哈希表,setf还可以用于更改集合包含的值。例如,使用向量: (defparameter *x* '(a b c d)) (setf (elt *x* 1) bb) 这让我对setf产生
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解释器实现?(或者,更简单地说,公共lisp标准对setf实现的确切定义)
我目前正在使用clisp,但欢迎对其他实现进行深入了解。是一个宏
你可以阅读所有血淋淋的细节,但基本上,它是这样工作的:
(setf符号表达式)
与
否则,我们有(setq(符号参数)表达式)
如果有一个名为(setf symbol)
(是的,您读对了,一个名为的函数的列表长度为2!),则会调用它
否则,将使用来自的定义生成“setf
扩展”
或者。
SETF
是一个将值设置到某个位置的宏。一个地方意味着一个有一个地方的形式。有多种内置功能,您可以定义更多(例如,请参见和、和)
您可以使用获取表单的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表单时,该变量名应绑定到第二个值中的表单返回的值。第三个值是存储变量列表,它应该绑定到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
用于绑定存储变量,因为可能有多个存储变量
(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)
hyperspec中有关于
SETF
/的详细信息,尽管它是相当技术性的。我一直认为defun
作为一个宏,它在表单(SETF符号)
上做了一些神奇的事情,但要意识到这是实际的函数名,确实很有帮助。还解释了为什么repl在输入该函数时返回该表单。我敢说,这似乎有点像语言中的一种“神奇方法”,它将保持无名:)(最后一点可能是错误的tho)。