setf如何在引擎盖下工作?

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,因为它最终会找到非平凡的信息,或制造黑魔法.我看到了多种可能性.

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
Run Code Online (Sandbox Code Playgroud)

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

3. setf是一种特殊的解释案例

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

4.有一些我不了解口齿不清的事情

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

奖金问题:是否依赖于lisp解释器实现的实现方法?(或者更简单地说,通常的lisp标准对setf实现的定义)我目前正在使用clisp,但欢迎对其他实现的见解.

jki*_*ski 7

SETF是一个为某个地方设置值的宏.地方是指具有setf扩展的表单.有各种各类场所内置的,你可以定义更多(例如见DEFSETFDEFINE-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)


sds*_*sds 6

setf 是一个宏.

您可以在所有血腥细节中阅读有关广义参考的内容,但基本上它的工作原理如下:

(setf symbol expression) 是相同的 setq

否则,我们有(setq (symbol arguments) expression).

如果有一个名为的函数(setf symbol)(是的,你读得正确,一个名为长度为2的列表的函数!),则调用它.

否则setf,使用来自defsetf或的定义生成" 扩展" define-setf-expander.