3

Currently learning common lisp, following Peter Seibel's Practical Common Lisp (i'm at chapter 11, about collections), i have difficulties to understand how setf works behind the hood.

Considering this expression :

(setf a 10)

I completely understood how the interpreter can (1) retrieve the variable named a, and (2) change the value it points to to 10.

Now, i case of particular collections, for instance lists, vectors or hash tables, setf can also be used to change values contained by the collection. For instance, with a vector :

(defparameter *x* '(a b c d))
(setf (elt *x* 1) bb)

This makes me suspicious about setf, because it is eventually finding non-trivially accessible information, or making black magic. I see multiple possibilities.

1. setf is a function

The (elt *x* 1) expression is returning 'b, so setf is virtually working with (setf b bb). I then do not understand how setf can infer which object (here, the list *x*) it must modify, without having the return value of elt holding both an indication that it comes from a collection, and a pointer to the said collection. Seems complicated.

2. setf is a macro

The idea is, as setf is a macro, it works directly with the (setf (elt *x* 1) bb) and therefore can extract the elt *x* 1 part to infer which object/collection is used, and therefore must be modified.

It do not seems very efficient, nor reliable, nor resistant to complex operations. However, since i'm unable to run this code :

(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

This make me think that setf is a macro implementing a very simple heuristic to retrieve the function to call, and all other needed information. Seems complicated.

3. setf is a special case of interpretation

Another way to go could be to have setf be handled differently by the interpreter itself, dealing with some black magic to implement properly the expected behavior. Seems complicated.

4. there is something i do not know about lisp

Probably the real answer. What did i miss ?

Bonus question : is the implementation method dependent of the lisp interpreter implementation ? (or, more simply, what exactly the common lisp standard define about setf implementation) I'm currently using clisp but insights on others implementations are welcome.

4

2 回答 2

10

SETF是一个将值设置为place的宏。一个地方意味着一个具有setf 扩展的表单。内置了多种场所,您可以定义更多(参见例如DEFSETFand DEFINE-SETF-EXPANDER函数调用形式为场所宏形式为场所)。

您可以使用. GET-SETF-EXPANSION它返回五个值。例如,

(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或只定义一个 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)
于 2017-06-22T13:08:25.810 回答
6

setf是一个宏。

您可以阅读有关通用参考的所有血腥细节,但基本上,它的工作方式如下:

(setf symbol expression)是相同的setq

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

如果有一个名为的函数(setf symbol)(是的,你没看错,一个以长度为 2 的列表命名的函数!),那么它就会被调用。

否则setf,使用来自 defsetfor的定义生成“扩展” define-setf-expander

于 2017-06-22T13:02:54.023 回答