About generalized variable in onlisp

2019-07-04 07:57发布

I am not sure what is going on here, a macro example in the text. Basically, not comfortable with how to use get-setf-method, a built-in macro (maybe function?). To be specific, how about the case that some of the return values of get-setf-method are nil? e.g. (get-setf-method 'x)

NIL ;
NIL ;
(#:NEW-3069) ;
(SETQ X #:NEW-3069) ;
X

And why this example code set the fifth return value to the second return value first, for initialization? Finally how it can handle the order of setting the variables in an expression, such as (aref ar (incf i)

(get-setf-method '(aref ar (incf i)))

(#:G3070 #:G3071) ;
(AR (INCF I)) ;
(#:G3072) ;
(SYSTEM::STORE #:G3070 #:G3071 #:G3072) ;
(AREF #:G3070 #:G3071)

Here is the definition of the macro :

(defmacro sortf (op &rest places)
  (let* ((meths (mapcar #'(lambda (p)
                             (multiple-value-list
                              (get-setf-method p)))
                        places))
         (temps (apply #'append (mapcar #'third meths))))
    `(let* ,(mapcar #'list
                     (mapcan #'(lambda (m)
                                  (append (first m)
                                          (third m)))
                             meths)
                     (mapcan #'(lambda (m)
                                  (append (second m)
                                          (list (fifth m))))
                             meths))
        ,@(mapcon #'(lambda (rest)
                       (mapcar
                        #'(lambda (arg)
                             `(unless (,op ,(car rest) ,arg)
                                 (rotatef ,(car rest) ,arg)))
                        (cdr rest)))
                  temps)
        ,@(mapcar #'fourth meths))))

1条回答
乱世女痞
2楼-- · 2019-07-04 08:44

That's actually some older code. get-setf-method was actually replaced by get-setf-expansion as described in Issue SETF-METHOD-VS-SETF-METHOD Writeup. So what you should be interested in these days is get-setf-expansion. The values that it returns are the pieces of code that you need to safely store a value in a location. This is very important because it's very easy to write modyfing macros incorrectly.

As to why some of the values can be nil, one of the examples in the documentation for get-setf-expansion actually shows how some of the values can be nil:

(get-setf-expansion 'x)
;=>  NIL, NIL, (#:G0001), (SETQ X #:G0001), X 

But what are those values? For that we need to look at the syntax of the documentation:

Syntax:

get-setf-expansion place &optional environment

vars, vals, store-vars, writer-form, reader-form

Arguments and Values:

place—a place.

environment—an environment object.

vars, vals, store-vars, writer-form, reader-form—a setf expansion.

Those five return values are described in 5.1.1.2 Setf Expansions:

List of temporary variables a list of symbols naming temporary variables to be bound sequentially, as if by let*, to values resulting from value forms.

List of value forms a list of forms (typically, subforms of the place) which when evaluated yield the values to which the corresponding temporary variables should be bound.

List of store variables a list of symbols naming temporary store variables which are to hold the new values that will be assigned to the place.

Storing form a form which can reference both the temporary and the store variables, and which changes the value of the place and guarantees to return as its values the values of the store variables, which are the correct values for setf to return.

Accessing form a form which can reference the temporary variables, and which returns the value of the place.

So what do those values in the example mean?

(get-setf-expansion 'x)
;⇒  NIL, NIL, (#:G0001), (SETQ X #:G0001), X 

To write to variable x, we don't need any temporary storage, and since there are no temporary values, we don't need any forms to produce values for them. We can notice here that the first and second values are always lists, and they should always have the same length. The third value is a list of store variables. This is a list, because we can actually use setf to modify multiple values, but in this case there's just one. The variables here are where the macro should actually store the new values for the place. Then, it's the writer-form (setq x #:g0001) that will actually take care of putting the value in the place. x, of course, is a simple way of reading the value.

As a more complex example, have a look at this transcript from SBCL:

CL-USER> (defstruct person
           person-name)
;⇒ PERSON

CL-USER> (get-setf-expansion '(char (person-name (first (second list-of-list-of-persons))) 3))
; (#:TMP965)
; ((PERSON-NAME (FIRST (SECOND LIST-OF-LIST-OF-PERSONS))))
; (#:NEW964)
; (SB-KERNEL:%CHARSET #:TMP965 3 #:NEW964)
; (CHAR #:TMP965 3)

This means that if we wanted to change the fourth character of the name of the first person in the second list of persons in a list of lists of persons, we could do it with:

(let* ((temp965 (person-name (first (second list-of-list-of-persons))))
       (old-char (char tmp965 3)))        ; optional 
  (setq new964 <compute-new-value>)
  (sb-kernel:%charset tmp965 3 new964))

We can compute the new value however we want (just fill in for <compute-new-value>), and we can even reference the old value if we want to (by including the optional line). All we need to do is set new964 to the new value, and then execute the writer-form that was given to us.

There are more examples of get-setf-expansion on Stack Overflow:

查看更多
登录 后发表回答