DEFINE-SETF-EXPANDER — setf로 place를 갱신하는 방법 정의하기
DEFINE-SETF-EXPANDER — setf로 place를 갱신하는 방법 정의하기 (매크로)
setf가 특정 place(보통 access-fn으로 참조되는 곳)를 갱신하는 방식을 직접 지정해주는 매크로예요. 다섯 값을 돌려주는 setf 확장을 직접 작성해서, 복잡한 place도 올바르게 갱신되도록 만들 수 있어요.
시그니처 (Syntax)
define-setf-expander access-fn lambda-list [[declaration* | documentation]] form* => access-fn
access-fn— function이나 macro를 name하는symbol.lambda-list— macro lambda list.declaration— declare expression. 평가되지 않아요.documentation—string. 평가되지 않아요.forms— implicit progn.
본문 (Description)
define-setf-expander는 setf가 access-fn으로 참조되는 place를 갱신하는 수단을 지정해요.
setf에 access-fn으로 표현된 place와 그 place의 새 값이 주어지면, 적절한 갱신을 수행하는 form으로 확장돼요.
lambda-list는 destructuring을 지원해요. Section 3.4.4 (Macro Lambda Lists)를 보세요.
documentation은 access-fn에 setf 종류의 documentation string으로 붙어요.
forms는 setf 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 form이 top level form으로 나타나면 compiler는 setf expander를 사용할 수 있게 만들어, 파일 뒤쪽에서 setf 호출을 확장할 때 쓰도록 해야 해요. Programmer는 access-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-expander는 defsetf의 긴 형식과 달라요. 본문이 실행되는 동안 lambda-list의 variable들은 place form의 일부에 직접 binding되지, 그런 부분의 값에 binding될 임시 변수에는 binding되지 않아요. 게다가 define-setf-expander에는 access-fn이 반드시 function이나 function-like macro여야 한다는 defsetf의 제약이 없어서, lambda-list에 임의의 defmacro destructuring 패턴을 쓸 수 있어요.