WITH-SLOTS — 슬롯을 변수처럼 다루는 매크로

WITH-SLOTS — 슬롯을 변수처럼 다루는 매크로

CLOS 객체의 슬롯 값을 읽고 쓰려면 보통 slot-value를 쓰는데, 매번 긴 표현을 반복하다 보면 코드가 지저분해져요. 슬롯을 마치 일반 변수처럼 쓸 수 있다면 훨씬 읽기 좋겠죠. with-slots가 바로 그 일을 해주는 매크로예요. 이 문서는 매크로(macro) 문서예요.

출처: CLHS WITH-SLOTS

본문

문법 (Syntax)

with-slots (slot-entry*) instance-form declaration* form*
=> result*

slot-entry ::= slot-name | (variable-name slot-name)

인자와 값 (Arguments and Values)

  • slot-name — 슬롯 이름(slot name)이에요. 평가되지 않아요.
  • variable-name — 변수 이름이에요. 평가되지 않아요.
  • instance-form — form이에요. 평가되어 instance를 만들어요.
  • instance — 객체(object)예요.
  • declarationdeclare 표현식이에요. 평가되지 않아요.
  • forms — implicit progn이에요.
  • resultsforms가 반환하는 값들이에요.

설명 (Description)

with-slotsinstance 안의 slot-name들이 가리키는 슬롯을 마치 변수처럼 참조할 수 있는 **어휘 환경(lexical environment)**을 확립해요. 그런 문맥에서 슬롯의 값은 슬롯 이름을 어휘 바인딩된 변수처럼 써서 읽을 수 있어요. 슬롯 값의 설정은 setfsetq를 모두 쓸 수 있어요.

with-slots는 변수처럼 나타난 슬롯 이름을 slot-value 호출로 번역해요.

예제 (Examples)

with-slots에서 슬롯 이름이 실제로 값에 접근하는지, 그리고 (variable-name slot-name) 형태로 다른 이름을 붙여 쓸 수 있는지 확인해 볼게요.

(defclass thing ()
          ((x :initarg :x :accessor thing-x)
           (y :initarg :y :accessor thing-y)))
=>  #<STANDARD-CLASS THING 250020173>
 (defmethod (setf thing-x) :before (new-x (thing thing))
   (format t "~&Changing X from ~D to ~D in ~S.~%"
           (thing-x thing) new-x thing))
 (setq thing (make-instance 'thing :x 0 :y 1)) =>  #<THING 62310540>
 (with-slots (x y) thing (incf x) (incf y)) =>  2
 (values (thing-x thing) (thing-y thing)) =>  1, 2
 (setq thing1 (make-instance 'thing :x 1 :y 2)) =>  #<THING 43135676>
 (setq thing2 (make-instance 'thing :x 7 :y 8)) =>  #<THING 43147374>
 (with-slots ((x1 x) (y1 y))
             thing1
   (with-slots ((x2 x) (y2 y))
               thing2
     (list (list x1 (thing-x thing1) y1 (thing-y thing1)
                 x2 (thing-x thing2) y2 (thing-y thing2))
           (setq x1 (+ y1 x2))
           (list x1 (thing-x thing1) y1 (thing-y thing1)
                 x2 (thing-x thing2) y2 (thing-y thing2))
           (setf (thing-x thing2) (list x1))
           (list x1 (thing-x thing1) y1 (thing-y thing1)
                 x2 (thing-x thing2) y2 (thing-y thing2)))))
>>  Changing X from 7 to (9) in #<THING 43147374>.
=>  ((1 1 2 2 7 7 8 8)
     9
     (9 9 2 2 7 7 8 8) 
     (9)
     (9 9 2 2 (9) (9) 8 8))

안쪽 with-slots에서 x1에 새 값을 setq하면 그 값(9)이 thing1의 슬롯에 반영되고, (setf (thing-x thing2) ...)는 밖의 with-slots가 이미 평가된 뒤라 x1 변수에는 영향이 없어요.

영향받는 요소 (Affected By)

  • defclass

예외 상황 (Exceptional Situations)

어떤 slot-nameinstance의 슬롯 이름이 아니면 결과가 정의되어 있지 않아요.

더 알아보기 (See Also)

  • with-accessors
  • slot-value
  • symbol-macrolet

참고 (Notes)

다음과 같은 with-slots 표현식은

(with-slots (slot-entry1 ... slot-entryn) instance-form form1 ... formk)

다음과 같은 코드로 확장돼요.

(let ((in instance-form))
  (symbol-macrolet (Q1 ... Qn) form1 ... formk))

여기서 Qislot-entryi가 심볼이면

(slot-entryi () (slot-value in 'slot-entryi))

이고, (variable-namei slot-namei) 형태면

(variable-namei () (slot-value in 'slot-namei))

이에요.