DEFINE-SETF-EXPANDER — setf로 place를 갱신하는 방법 정의하기

DEFINE-SETF-EXPANDER — setf로 place를 갱신하는 방법 정의하기 (매크로)

setf가 특정 place(보통 access-fn으로 참조되는 곳)를 갱신하는 방식을 직접 지정해주는 매크로예요. 다섯 값을 돌려주는 setf 확장을 직접 작성해서, 복잡한 place도 올바르게 갱신되도록 만들 수 있어요.

출처: DEFINE-SETF-EXPANDER - Common Lisp HyperSpec

시그니처 (Syntax)

define-setf-expander access-fn lambda-list [[declaration* | documentation]] form* => access-fn
  • access-fnfunction이나 macroname하는 symbol.
  • lambda-listmacro lambda list.
  • declarationdeclare expression. 평가되지 않아요.
  • documentationstring. 평가되지 않아요.
  • formsimplicit progn.

본문 (Description)

define-setf-expandersetfaccess-fn으로 참조되는 place를 갱신하는 수단을 지정해요.

setfaccess-fn으로 표현된 place와 그 place의 새 값이 주어지면, 적절한 갱신을 수행하는 form으로 확장돼요.

lambda-list는 destructuring을 지원해요. Section 3.4.4 (Macro Lambda Lists)를 보세요.

documentationaccess-fnsetf 종류의 documentation string으로 붙어요.

formssetf expander 정의의 본문이 되고, 주어진 access-fn으로 place를 참조하는 setf 호출에 대한 setf expansion을 계산해야 해요. setf expander 함수는 define-setf-expander form이 나타나는 것과 같은 lexical environment에서 정의돼요. forms가 실행되는 동안 lambda-list의 변수들은 place form의 일부에 binding돼요. define-setf-expander form의 본문 forms(lambda-list는 아님)는 access-fn을 이름으로 하는 block 안에 암묵적으로 감싸져요.

forms의 평가는 Section 5.1.1.2 (Setf Expansions)에서 설명하는 다섯 값을 반드시 결과로 내야 해요.

define-setf-expander formtop level form으로 나타나면 compilersetf expander를 사용할 수 있게 만들어, 파일 뒤쪽에서 setf 호출을 확장할 때 쓰도록 해야 해요. Programmeraccess-fn이 같은 파일 뒤쪽의 place에서 쓰인다면 forms를 컴파일 시점에 평가할 수 있도록 보장해야 해요. compiler는 매크로의 environment parameter로 받은 값을 environment 인자로 하는 컴파일 시점의 get-setf-expansion 호출에도 이 setf expander를 사용할 수 있게 해야 해요.

예제 (Examples)

(defun lastguy (x) (car (last x))) =>  LASTGUY

(define-setf-expander lastguy (x &environment env)
  "Set the last element in a list to the given value."
  (multiple-value-bind (dummies vals newval setter getter)
      (get-setf-expansion x env)
    (let ((store (gensym)))
      (values dummies
              vals
              `(,store)
              `(progn (rplaca (last ,getter) ,store) ,store)
              `(lastguy ,getter))))) =>  LASTGUY

(setq a (list 'a 'b 'c 'd)
      b (list 'x)
      c (list 1 2 3 (list 4 5 6))) =>  (1 2 3 (4 5 6))

(setf (lastguy a) 3) =>  3
(setf (lastguy b) 7) =>  7
(setf (lastguy (lastguy c)) 'lastguy-symbol) =>  LASTGUY-SYMBOL

a =>  (A B C 3)
b =>  (7)
c =>  (1 2 3 (4 5 LASTGUY-SYMBOL))
;;; Setf expander for the form (LDB bytespec int).
;;; Recall that the int form must itself be suitable for SETF.
(define-setf-expander ldb (bytespec int &environment env)
  (multiple-value-bind (temps vals stores
                         store-form access-form)
      (get-setf-expansion int env)  ;Get setf expansion for int.
    (let ((btemp (gensym))     ;Temp var for byte specifier.
          (store (gensym))     ;Temp var for byte to store.
          (stemp (first stores))) ;Temp var for int to store.
      (if (cdr stores) (error "Can't expand this."))
;;; Return the setf expansion for LDB as five values.
      (values (cons btemp temps)       ;Temporary variables.
              (cons bytespec vals)     ;Value forms.
              (list store)             ;Store variables.
              `(let ((,stemp (dpb ,store ,btemp ,access-form)))
                 ,store-form
                 ,store)               ;Storing form.
              `(ldb ,btemp ,access-form) ;Accessing form.
             ))))

더 알아보기 (See Also)

setf, defsetf, documentation, get-setf-expansion, Section 3.4.11 (Syntactic Interaction of Documentation Strings and Declarations)

Notes

define-setf-expanderdefsetf의 긴 형식과 달라요. 본문이 실행되는 동안 lambda-listvariable들은 place form의 일부에 직접 binding되지, 그런 부분의 값에 binding될 임시 변수에는 binding되지 않아요. 게다가 define-setf-expander에는 access-fn이 반드시 function이나 function-like macro여야 한다는 defsetf의 제약이 없어서, lambda-list에 임의의 defmacro destructuring 패턴을 쓸 수 있어요.