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)