目前正在学习常见的口齿不清,跟随Peter Seibel的 Practical Common Lisp (我在第11章,关于馆藏),我很难理解setf
如何在幕后工作。< / p>
考虑这个表达式:
(setf a 10)
我完全理解解释器如何(1)检索名为a
的变量,以及(2)将其指向的值更改为10
。
现在,我是特定集合的例子,例如列表,向量或散列表,setf也可用于更改集合包含的值。例如,使用向量:
(defparameter *x* '(a b c d))
(setf (elt *x* 1) bb)
这使我对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
这让我觉得setf
是一个宏,它实现了一个非常简单的启发式方法来检索要调用的函数以及所有其他所需的信息。
似乎很复杂。
另一种方法可能是让解释器本身以不同的方式处理setf,处理一些黑魔法以正确实现预期的行为。 似乎很复杂。
可能是真正的答案。我错过了什么?
奖金问题:是否依赖于lisp解释器实现的实现方法? (或者更简单地说,通常的lisp标准对setf实现的定义是什么) 我目前正在使用 clisp ,但欢迎对其他实施的见解。
答案 0 :(得分:7)
SETF
是一个将值设置为地点的宏。地方表示具有setf expansion的表单。内置了多种kinds of places,您可以定义更多内容(请参阅例如DEFSETF
和DEFINE-SETF-EXPANDER
,function call forms as places和macro 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表单时绑定到第二个值中表单返回的值。第三个值是存储变量列表,它应该绑定到由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)
答案 1 :(得分:6)
setf
是一个宏。
你可以在所有血腥细节中阅读Generalized Reference,但基本上,它的工作原理如下:
(setf symbol expression)
与setq
否则,我们有(setq (symbol arguments) expression)
。
如果有一个名为(setf symbol)
的函数(是的,你读得正确,一个名为长度为2的列表的函数!),那么就会调用它。
否则使用来自的定义生成“setf
扩展”
defsetf
或define-setf-expander
。