DEFSETF — setf로 갱신 가능한 place 정의하기
DEFSETF — setf로 갱신 가능한 place 정의하기 (매크로)
setf로 갱신할 수 있는 place의 갱신 방법을 지정해주는 매크로예요. 비교적 단순한 형태의 (access-fn ...) place를 짧은 형식이나 긴 형식으로 정의해서, 일반 함수 호출처럼 보이는 place를 갱신할 수 있게 해요.
시그니처 (Syntax)
short form:
defsetf access-fn update-fn [documentation] => access-fn
long form:
defsetf access-fn lambda-list (store-variable*) [[declaration* | documentation]] form*
=> access-fn
access-fn— function이나 macro를 가리키는symbol.update-fn— function이나 macro를 가리키는symbol.lambda-list— defsetf lambda list.store-variable—symbol(variable name).declaration— declare expression. 평가되지 않아요.documentation—string. 평가되지 않아요.form—form.
본문 (Description)
defsetf는 비교적 단순한 경우에 (access-fn ...) 형태의 place를 setf하는 방법을 정의해요. (더 일반적인 접근은 define-setf-expander를 보세요.) access-fn이 가리키는 function이나 macro는 모든 인자를 평가해야 해요.
defsetf는 "short form"과 "long form" 두 형태 중 하나를 취할 수 있는데, 둘은 두 번째 argument의 type으로 구분돼요.
short form을 쓰면 update-fn은 access-fn이 받는 것보다 인자를 하나 더 받는 function(이나 macro)을 가리켜야 해요. setf에 access-fn을 호출하는 place가 주어지면, access-fn의 모든 인자와, 마지막 인자로 새 값(update-fn이 값으로 돌려줘야 함)을 받는 update-fn 호출로 확장돼요.
long form defsetf는 defmacro와 비슷해요. lambda-list는 access-fn의 인자를 기술하고, store-variable은 place에 저장될 값(들)을 기술해요. body는 access-fn 호출에 대한 setf의 확장을 계산해야 해요. 확장 함수는 defsetf form이 나타나는 것과 같은 lexical environment에서 정의돼요.
form을 평가하는 동안 lambda-list의 변수와 store-variable은, gensym이나 gentemp처럼 생성된 임시 변수 이름에 binding돼요. 그 임시 변수들은 setf의 확장에 의해 그 subform의 값으로 binding될 거예요. 이 binding 덕분에 form을 순서 평가(order-of-evaluation) 문제를 염려하지 않고 쓸 수 있어요. defsetf는 가능한 경우 임시 변수가 최종 결과에서 최적화되어 빠지도록 처리해요.
defsetf의 body 코드는 access-fn이라는 이름의 block 안에 암묵적으로 감싸여 있어요.
defsetf는 place의 subform이 정확히 한 번 평가되도록 보장해요.
documentation은 access-fn에 setf 종류의 documentation string으로 붙어요.
defsetf form이 top level form으로 나타나면, compiler는 setf expander를 사용 가능하게 만들어 파일 뒤쪽의 setf 호출을 확장할 때 쓰게 해야 해요. 사용자는 access-fn이 같은 파일 뒤쪽의 place에서 쓰인다면 form을 컴파일 시점에 평가할 수 있게 보장해야 해요. compiler는 매크로의 environment parameter로 받은 값을 environment 인자로 하는 컴파일 시점 get-setf-expansion 호출에도 이 setf expander를 사용할 수 있게 해야 해요.
예제 (Examples)
(defsetf symbol-value set)의 효과는 Common Lisp 시스템에 내장되어 있어요. 이로 인해 (setf (symbol-value foo) fu) 형태가 (set foo fu)로 확장돼요.
(defsetf car rplaca)는 올바르지 않다는 점을 유의하세요. rplaca는 마지막 인자를 돌려주지 않기 때문이에요.
(defun middleguy (x) (nth (truncate (1- (list-length x)) 2) x)) => MIDDLEGUY
(defun set-middleguy (x v)
(unless (null x)
(rplaca (nthcdr (truncate (1- (list-length x)) 2) x) v))
v) => SET-MIDDLEGUY
(defsetf middleguy set-middleguy) => MIDDLEGUY
(setq a (list 'a 'b 'c 'd)
b (list 'x)
c (list 1 2 3 (list 4 5 6) 7 8 9)) => (1 2 3 (4 5 6) 7 8 9)
(setf (middleguy a) 3) => 3
(setf (middleguy b) 7) => 7
(setf (middleguy (middleguy c)) 'middleguy-symbol) => MIDDLEGUY-SYMBOL
a => (A 3 C D)
b => (7)
c => (1 2 3 (4 MIDDLEGUY-SYMBOL 6) 7 8 9)
long form defsetf 사용 예:
(defsetf subseq (sequence start &optional end) (new-sequence)
`(progn (replace ,sequence ,new-sequence
:start1 ,start :end1 ,end)
,new-sequence)) => SUBSEQ
(defvar *xy* (make-array '(10 10)))
(defun xy (&key ((x x) 0) ((y y) 0)) (aref *xy* x y)) => XY
(defun set-xy (new-value &key ((x x) 0) ((y y) 0))
(setf (aref *xy* x y) new-value)) => SET-XY
(defsetf xy (&key ((x x) 0) ((y y) 0)) (store)
`(set-xy ,store 'x ,x 'y ,y)) => XY
(get-setf-expansion '(xy a b))
=> (#:t0 #:t1),
(a b),
(#:store),
((lambda (&key ((x #:x)) ((y #:y)))
(set-xy #:store 'x #:x 'y #:y))
#:t0 #:t1),
(xy #:t0 #:t1)
(xy 'x 1) => NIL
(setf (xy 'x 1) 1) => 1
(xy 'x 1) => 1
(let ((a 'x) (b 'y))
(setf (xy a 1 b 2) 3)
(setf (xy b 5 a 9) 14))
=> 14
(xy 'y 0 'x 1) => 1
(xy 'x 1 'y 2) => 3
더 알아보기 (See Also)
documentation, setf, define-setf-expander, get-setf-expansion, Section 5.1 (Generalized Reference), Section 3.4.11 (Syntactic Interaction of Documentation Strings and Declarations)
Notes
form은 올바른 값(store-variable의 값(들))을 돌려주는 처리를 포함해야 해요. 이것은 defsetf가 아니라 form이 처리하는데, 많은 경우 그 값을 추가 비용 없이 돌려줄 수 있기 때문이에요. 즉 place에 저장하면서 동시에 올바른 값을 돌려주는 함수를 호출하면 되니까요.
access-fn 호출에 대한 setf도 access-fn의 모든 인자를 평가해요. 그 중 어떤 것도 특별히 취급할 수 없어요. 이는 defsetf로 (ldb field reference) 같은 바이트에 대한 generalized reference 저장 방법을 기술할 수 없다는 뜻이에요. define-setf-expander는 defsetf가 부과하는 제약에 맞지 않는 상황을 처리하는 데 쓰이며, 사용자에게 추가 제어를 줘요.