alu*_*iak 3 common-lisp setf lisp-macros
目前正在学习常见的口齿不清,继Peter Seibel的Practical Common Lisp(我在第11章,关于收藏)之后,我很难理解setf
幕后的工作方式.
考虑这个表达式:
(setf a 10)
Run Code Online (Sandbox Code Playgroud)
我完全理解解释器如何(1)检索名为的变量a
,以及(2)更改它指向的值10
.
现在,我是特定集合的例子,例如列表,向量或散列表,setf也可用于更改集合包含的值.例如,使用向量:
(defparameter *x* '(a b c d))
(setf (elt *x* 1) bb)
Run Code Online (Sandbox Code Playgroud)
这让我怀疑setf
,因为它最终会找到非平凡的信息,或制造黑魔法.我看到了多种可能性.
该(elt *x* 1)
表达式返回'b
,因此setf
实际上被有工作(setf b bb)
.然后我不明白如何setf
推断*x*
它必须修改哪个对象(这里是列表),而不具有elt
保持它来自集合的指示的返回值,以及指向所述集合的指针.看似复杂.
这个想法就像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
Run Code Online (Sandbox Code Playgroud)
这让我觉得这setf
是一个实现一个非常简单的启发式的宏来检索要调用的函数,以及所有其他所需的信息.看似复杂.
另一种方法可能是让解释器本身对setf进行不同的处理,处理一些黑魔法以正确实现预期的行为.看似复杂.
可能是真正的答案.我错过了什么 ?
奖金问题:是否依赖于lisp解释器实现的实现方法?(或者更简单地说,通常的lisp标准对setf实现的定义)我目前正在使用clisp,但欢迎对其他实现的见解.
SETF
是一个为某个地方设置值的宏.地方是指具有setf扩展的表单.有各种各类场所内置的,你可以定义更多(例如见DEFSETF
和DEFINE-SETF-EXPANDER
,函数调用形式的地方和宏观形式的地方).
您可以使用表单获取表单的setf扩展GET-SETF-EXPANSION
.它返回五个值.例如,
(get-setf-expansion '(elt *x* 1))
;=> (#:*X*660)
; (*X*)
; (#:NEW1)
; (SB-KERNEL:%SETELT #:*X*660 1 #:NEW1)
; (ELT #:*X*660 1)
Run Code Online (Sandbox Code Playgroud)
第五个值是一个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))))
Run Code Online (Sandbox Code Playgroud)
我们需要做的就是绑定变量,并评估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)
Run Code Online (Sandbox Code Playgroud)
有多种方法可以定义自己的位置.最简单的方法是使用DEFSETF
或仅定义setf函数DEFUN
.例如:
(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)
Run Code Online (Sandbox Code Playgroud)