DEFINE-METHOD-COMBINATION — method combination 타입 정의하기
DEFINE-METHOD-COMBINATION — method combination 타입 정의하기 (매크로)
새 method combination 타입을 정의하는 매크로예요. CLOS에서 여러 메서드가 하나의 호출로 어떻게 합쳐질지(primary·before·after·around 등) 결정하는 규칙을 직접 만들 수 있어요. short form은 자주 쓰는 단순한 케이스를, long form은 더 강력하지만 장황한 확장을 지원해요.
시그니처 (Syntax)
define-method-combination name [[short-form-option]] => name
define-method-combination name lambda-list (method-group-specifier*)
[(:arguments . args-lambda-list)] [(:generic-function generic-function-symbol)]
[[declaration* | documentation]] form* => name
short-form-option ::= :documentation documentation |
:identity-with-one-argument identity-with-one-argument |
:operator operator
method-group-specifier ::= (name {qualifier-pattern+ | predicate} [[long-form-option]])
long-form-option ::= :description description |
:order order |
:required required-p
args-lambda-list— define-method-combination arguments lambda list.declaration— declare expression. 평가되지 않아요.description— format control.documentation—string. 평가되지 않아요.forms— method들이 어떻게 합쳐질지, 즉 effective method를 지정하는 form을 계산해 돌려줘야 하는 implicit progn.generic-function-symbol—symbol.identity-with-one-argument— generalized boolean.lambda-list— ordinary lambda list.name—symbol. 보통 keyword가 아니고 non-nil인 symbol을 써요.operator— operator. Name과 operator는 흔히 같은 symbol이에요. 이것이 기본값이지만 필수는 아니에요.order— :most-specific-first 또는 :most-specific-last. 평가돼요.predicate— 한 인자를 받아 generalized boolean을 돌려주는 function을 가리키는symbol.qualifier-pattern—list이거나 symbol*.required-p— generalized boolean.
본문 (Description)
매크로 define-method-combination은 새 method combination 타입을 정의하는 데 쓰여요.
define-method-combination에는 두 형태가 있어요. short form은 가장 흔히 필요할 것으로 예상되는 케이스를 위한 단순한 기능이고, long form은 더 강력하지만 더 장황해요. 본문이 (보통 backquote를 쓰는) form을 계산하는 표현식이라는 점에서 defmacro와 비슷해요. 따라서 임의의 제어 구조를 구현할 수 있어요. long form은 method qualifier의 임의 처리도 허용해요.
Short Form
define-method-combination의 short form 문법은 두 번째 subform이 non-nil symbol이거나 없을 때 인식돼요. short form을 쓰면 name은 (operator method-call method-call ...) 형태의 Lisp form을 만드는 method combination 타입으로 정의돼요. operator는 function·macro·special operator의 name이 될 수 있는 symbol이에요. operator는 키워드 옵션으로 제공될 수 있고, 기본값은 name이에요.
short form의 키워드 옵션들은 이래요:
- :documentation — method-combination 타입을 문서화하는 데 쓰여요. 아래 long form 설명을 보세요.
- :identity-with-one-argument — 값이 true(기본값은 false)면 최적화를 활성화해요. 적용 가능한 method가 정확히 하나이고 그것이 primary method면, 그 method가 effective method가 되고 operator는 호출되지 않아요. 이 최적화는 새 effective method를 만들 필요를 없애고 function 호출 오버헤드를 피해요. 이 옵션은 progn, and,
+, max 같은 연산자와 함께 쓰도록 설계됐어요. - :operator — 연산자의 name을 지정해요. operator 인자는 function·macro·special form의 name이 될 수 있는
symbol이에요.
이런 method combination 타입은 method마다 정확히 하나의 qualifier를 요구해요. qualifier가 없거나 method combination 타입이 지원하지 않는 qualifier를 가진 적용 가능한 method가 있으면 에러가 신호돼요.
이렇게 정의된 method combination 프로시저는 method에 두 역할을 인식해요. 하나의 qualifier가 이 method combination 타입을 가리키는 symbol인 method는 primary method로 정의돼요. 적용 가능한 primary method가 하나 이상 있어야 하며, 그렇지 않으면 에러가 신호돼요. 유일한 qualifier가 :around인 method는 표준 method combination의 around method처럼 동작하는 보조 method예요. call-next-method function은 around method에서만 쓸 수 있고, define-method-combination 매크로의 short form이 정의한 primary method에서는 쓸 수 없어요.
이렇게 정의된 method combination 프로시저는 :most-specific-first를 기본값으로 하는 order라는 optional 인자를 받아들여요. :most-specific-last 값은 보조 method의 순서에는 영향 없이 primary method의 순서를 뒤집어요.
short form은 에러 검사와 around method 지원을 자동으로 포함해요.
내장 method combination 타입에 대한 논의는 Section 7.6.6.4 (Built-in Method Combination Types)를 보세요.
Long Form
define-method-combination의 long form 문법은 두 번째 subform이 list일 때 인식돼요.
lambda-list는 defgeneric의 :method-combination 옵션에서 method combination 타입의 name 뒤에 제공된 인자들을 받아요.
그 뒤에 method group specifier 목록이 와요. 각 specifier는 적용 가능한 method의 부분집합을 선택해 특정 역할을 맡기는데, 그 qualifier들을 어떤 패턴과 매칭하거나 predicate로 검사해요. 이 method group specifier들은 이 method combination 타입에 쓸 수 있는 모든 method qualifier를 정의해요.
각 method-group-specifier의 car는 variable을 가리키는 symbol이에요. define-method-combination 본문의 form이 실행되는 동안 이 variable은 method group 안의 method 목록에 binding돼요. 이 목록의 method들은 :order 옵션이 지정한 순서로 나타나요.
qualifier-pattern이 symbol이면 반드시 *여야 해요. method의 qualifier 목록이 qualifier-pattern과 equal이면 그 method가 qualifier-pattern과 일치해요(qualifier-pattern의 * 심볼은 무엇이든 일치한다는 점 제외). 따라서 qualifier-pattern은 다음 중 하나가 될 수 있어요: unqualified method와 일치하는 empty list; 모든 method와 일치하는 * 심볼; 각 qualifier가 대응하는 list 요소와 일치할 때 method의 qualifier 수가 list 길이와 같은 method와 일치하는 true list; 또는 * 심볼로 끝나는 dotted list(*는 추가 qualifier를 원하는 만큼 일치시킴).
각 적용 가능한 method는 qualifier-pattern과 predicate에 왼쪽에서 오른쪽 순서로 검사돼요. qualifier-pattern이 일치하거나 predicate가 true를 돌려주는 즉시, method는 대응하는 method group의 일원이 되고 더 이상 검사하지 않아요. 따라서 method가 둘 이상의 method group의 일원이 될 수 있으면 첫 번째 group에만 들어가요. method group에 qualifier-pattern이 둘 이상 있으면 method는 그 중 하나만 만족해도 group의 일원이 돼요.
predicate function의 name이 method group specifier에서 qualifier-pattern 대신 나타날 수 있어요. predicate는 이전 method group에 할당되지 않은 각 method에 대해 호출되는데, 한 인자(그 method의 qualifier list)로 호출돼요. predicate는 method가 해당 method group의 일원이어야 하면 true를 돌려줘야 해요. predicate는 nil이나 *가 아닌 symbol이므로 qualifier-pattern과 구분할 수 있어요.
어떤 method group에도 들어가지 않는 적용 가능한 method가 있으면 function invalid-method-error가 호출돼요.
Method group specifier는 qualifier 패턴이나 predicate 뒤에 키워드 옵션을 가질 수 있어요. 키워드 옵션은 list도 * 심볼도 아니므로 추가 qualifier 패턴과 구분돼요. 키워드 옵션들은 이래요:
- :description — method group 안 method들의 역할에 대한 설명을 제공하는 데 쓰여요. 프로그래밍 환경 도구는
(apply #'format stream *format-control* (method-qualifiers *method*))로 이 설명을 출력하는데, 간결하길 기대해요. 이 키워드 옵션은 method qualifier의 설명이 그 qualifier의 의미를 정의하는 모듈과 같은 곳에서 정의되게 해요. 대부분의 경우 format-control에는 format 지시어가 없지만, 일반성을 위해 쓸 수 있어요. :description이 제공되지 않으면 변수 이름과 qualifier 패턴, 그리고 이 method group이 unqualified method를 포함하는지에 따라 기본 설명이 생성돼요. - :order — method 순서를 지정해요. order 인자는 :most-specific-first 또는 :most-specific-last로 평가되는 form이에요. 다른 값으로 평가되면 에러가 신호돼요. :order가 제공되지 않으면 :most-specific-first로 기본 설정돼요.
- :required — 이 method group에 method가 하나 이상 필요한지 지정해요. 값이 true이고 method group이 비어 있으면(즉 적용 가능한 method 중 qualifier 패턴과 일치하거나 predicate를 만족하는 게 없으면) 에러가 신호돼요. :required가 제공되지 않으면 nil로 기본 설정돼요.
Method group specifier 사용은 method를 선택하고, 가능한 역할들 사이에 나누고, 필요한 에러 검사를 수행하는 편리한 문법을 제공해요. body form에서 일반 list 처리 연산과 method-qualifiers, invalid-method-error 함수를 써서 method를 더 필터링하는 것도 가능해요. method group specifier에서 이름 붙은 변수에 setq를 쓰고 추가 변수를 binding하는 것도 허용돼요. method group specifier 메커니즘을 우회해서 모든 것을 body form에서 하는 것도 가능해요. 이것은 유일한 qualifier-pattern이 *인 method group 하나를 써서 이루어지는데, 그러면 변수가 모든 applicable method의 list에 most-specific-first 순서로 binding돼요.
body form은 method들이 어떻게 합쳐질지, 즉 effective method를 지정하는 form을 계산해 돌려줘요. effective method는 call-method의 로컬 매크로 정의와 COMMON-LISP-USER 패키지에서 accessible하지 않은 symbol들이 가리키는 binding으로 보강된 null lexical environment에서 평가돼요. method group specifier들이 만든 list 중 하나에 있는 method object와 next method 목록이 주어지면, call-method은 call-next-method이 next method를 쓸 수 있도록 method를 호출해요.
effective method가 단일 method를 호출하는 것 외에는 효과가 없을 때, 일부 구현은 단일 method를 effective method로 직접 쓰는 최적화를 써서 새 effective method를 만드는 필요를 피해요. 이 최적화는 effective method form이 전적으로 call-method 매크로 호출로 이루어져 있고 그 첫 번째 subform이 method object이며 두 번째 subform이 nil이거나 제공되지 않을 때 활성화돼요. 각 define-method-combination body는 이 최적화를 원한다면 progn, and, multiple-value-prog1 등의 중복 호출을 벗겨내는 책임이 있어요.
(:arguments . lambda-list) 목록은 어떤 선언이나 documentation string보다 앞에 나타날 수 있어요. 이 형식은 method combination 타입이 결합된 method의 일부로 특정 동작을 수행하고, 그 동작이 generic function의 인자에 접근해야 할 때 유용해요. lambda-list가 정의한 각 매개변수 변수는 effective method에 삽입할 수 있는 form에 binding돼요. 이 form이 effective method 실행 중 평가되면 그 값은 generic function에 대한 대응 인자예요. 그런 form을 setf form의 place로 쓰는 결과는 정의되지 않아요. 인자 대응은 :arguments lambda-list와 generic function lambda-list를 세 부분(required 매개변수, optional 매개변수, keyword·rest 매개변수)으로 나눠서 계산돼요. 특정 call에서 generic function에 제공된 arguments도 세 부분으로 나눠져요. required arguments 부분은 generic function이 가진 required parameter 수만큼의 argument를 포함하고, optional arguments 부분은 optional parameter 수만큼, keyword/rest arguments 부분은 나머지 인자를 포함해요. :arguments lambda-list의 required·optional 부분의 각 parameter는 arguments의 대응 부분에서 같은 위치의 인자에 접근해요. :arguments lambda-list 부분이 더 짧으면 추가 argument는 무시돼요. 더 길면 초과 required parameter는 nil로 평가되는 form에, 초과 optional parameter는 자신의 initform에 bound돼요. :arguments lambda-list의 keyword parameter와 rest parameter는 arguments의 keyword/rest 부분에 접근해요. :arguments lambda-list가 &key를 포함하면 &allow-other-keys도 포함한 것처럼 동작해요.
게다가 :arguments lambda-list 맨 앞에 &whole var를 둘 수 있어요. 그러면 var가 generic function에 제공된 모든 argument의 list로 evaluate되는 form에 bound돼요. 이것은 &rest와 다른데, keyword/rest argument뿐 아니라 모든 인자에 접근하기 때문이에요.
본문이 감지한 오류 조건은 method-combination-error나 invalid-method-error로 보고해야 돼요. 이 function들은 에러 메시지에 필요한 맥락 정보를 추가하고 적절한 에러를 신호해요.
body form은 lambda list와 method group specifier가 만든 binding 안에서 평가돼요. body 앞머리의 선언은 lambda list가 만든 binding 바로 안쪽, method group 변수의 binding 바깥에 위치해요. 따라서 method group 변수는 이런 방식으로 선언할 수 없어요. 다만 locally를 body 주위에 쓸 수는 있어요.
body form 안에서 generic-function-symbol은 generic function object에 binding돼요.
documentation은 name에 method-combination 종류의 documentation string으로, 그리고 method combination object에도 붙어요.
특수화자가 동일하지만 qualifier가 다른 두 method는 Section 7.6.6 (Method Selection and Combination)의 method 선택·결합 과정 Step 2에서 설명한 알고리즘으로 정렬되지 않는다는 점을 유의하세요. 보통 두 method는 qualifier가 달라 effective method에서 서로 다른 역할을 하고, Step 2 결과에서 어떻게 정렬되든 effective method는 같아요. 두 method가 같은 역할을 하고 그 순서가 중요하면 에러가 신호돼요. 이것은 define-method-combination의 qualifier 패턴 매칭의 일부로 일어나요.
define-method-combination form이 top level form으로 나타나면, compiler는 method combination name이 이후 defgeneric form에서 유효한 method combination name으로 인식되게 해야 해요. 다만 method combination은 define-method-combination form이 실행될 때보다 이르지 않게, 그리고 그 method combination을 쓰는 generic function이 실행되는 시점까지 늦게 실행될 수 있어요.
예제 (Examples)
long form define-method-combination의 대부분의 예는 선언적 method combination 기능의 일부로 제공되는 관련 function 사용도 함께 보여줘요.
;;; Examples of the short form of define-method-combination
(define-method-combination and :identity-with-one-argument t)
(defmethod func and ((x class1) y) ...)
;;; The equivalent of this example in the long form is:
(define-method-combination and
(&optional (order :most-specific-first))
((around (:around))
(primary (and) :order order :required t))
(let ((form (if (rest primary)
`(and ,@(mapcar #'(lambda (method)
`(call-method ,method))
primary))
`(call-method ,(first primary)))))
(if around
`(call-method ,(first around)
(,@(rest around)
(make-method ,form)))
form)))
;;; Examples of the long form of define-method-combination
;The default method-combination technique
(define-method-combination standard ()
((around (:around))
(before (:before))
(primary () :required t)
(after (:after)))
(flet ((call-methods (methods)
(mapcar #'(lambda (method)
`(call-method ,method))
methods)))
(let ((form (if (or before after (rest primary))
`(multiple-value-prog1
(progn ,@(call-methods before)
(call-method ,(first primary)
,(rest primary)))
,@(call-methods (reverse after)))
`(call-method ,(first primary)))))
(if around
`(call-method ,(first around)
(,@(rest around)
(make-method ,form)))
form))))
;A simple way to try several methods until one returns non-nil
(define-method-combination or ()
((methods (or)))
`(or ,@(mapcar #'(lambda (method)
`(call-method ,method))
methods)))
;A more complete version of the preceding
(define-method-combination or
(&optional (order ':most-specific-first))
((around (:around))
(primary (or)))
;; Process the order argument
(case order
(:most-specific-first)
(:most-specific-last (setq primary (reverse primary)))
(otherwise (method-combination-error "~S is an invalid order.~@
:most-specific-first and :most-specific-last are the possible values."
order)))
;; Must have a primary method
(unless primary
(method-combination-error "A primary method is required."))
;; Construct the form that calls the primary methods
(let ((form (if (rest primary)
`(or ,@(mapcar #'(lambda (method)
`(call-method ,method))
primary))
`(call-method ,(first primary)))))
;; Wrap the around methods around that form
(if around
`(call-method ,(first around)
(,@(rest around)
(make-method ,form)))
form)))
;The same thing, using the :order and :required keyword options
(define-method-combination or
(&optional (order ':most-specific-first))
((around (:around))
(primary (or) :order order :required t))
(let ((form (if (rest primary)
`(or ,@(mapcar #'(lambda (method)
`(call-method ,method))
primary))
`(call-method ,(first primary)))))
(if around
`(call-method ,(first around)
(,@(rest around)
(make-method ,form)))
form)))
;This short-form call is behaviorally identical to the preceding
(define-method-combination or :identity-with-one-argument t)
;Order methods by positive integer qualifiers
;:around methods are disallowed to keep the example small
(define-method-combination example-method-combination ()
((methods positive-integer-qualifier-p))
`(progn ,@(mapcar #'(lambda (method)
`(call-method ,method))
(stable-sort methods #'<
:key #'(lambda (method)
(first (method-qualifiers method)))))))
(defun positive-integer-qualifier-p (method-qualifiers)
(and (= (length method-qualifiers) 1)
(typep (first method-qualifiers) '(integer 0 *))))
;;; Example of the use of :arguments
(define-method-combination progn-with-lock ()
((methods ()))
(:arguments object)
`(unwind-protect
(progn (lock (object-lock ,object))
,@(mapcar #'(lambda (method)
`(call-method ,method))
methods))
(unlock (object-lock ,object))))
Side Effects
compiler는 어떤 컴파일 타임 부수효과도 수행할 의무가 없어요.
예외 상황 (Exceptional Situations)
short form으로 정의된 method combination 타입은 method마다 정확히 하나의 qualifier를 요구해요. qualifier가 없거나 method combination 타입이 지원하지 않는 qualifier를 가진 적용 가능한 method가 있으면 type이 error인 에러가 신호돼요. 적용 가능한 primary method가 하나 이상 있어야 하며, 그렇지 않으면 type이 error인 에러가 신호돼요.
어떤 method group에도 들어가지 않는 적용 가능한 method가 있으면, 시스템은 그 method가 쓰는 method combination 종류에 유효하지 않다고 알리는 type이 error인 에러를 신호해요.
:required 옵션 값이 true이고 method group이 비어 있으면(qualifier 패턴과 일치하거나 predicate를 만족하는 적용 가능한 method가 없으면) type이 error인 에러가 신호돼요.
:order 옵션이 :most-specific-first나 :most-specific-last 외의 값으로 평가되면 type이 error인 에러가 신호돼요.
더 알아보기 (See Also)
call-method, call-next-method, documentation, method-qualifiers, method-combination-error, invalid-method-error, defgeneric, Section 7.6.6 (Method Selection and Combination), Section 7.6.6.4 (Built-in Method Combination Types), Section 3.4.11 (Syntactic Interaction of Documentation Strings and Declarations)
Notes
defgeneric의 :method-combination 옵션은 generic function이 특정 method combination 타입을 쓰도록 지정하는 데 쓰여요. :method-combination 옵션의 첫 번째 인자는 method combination 타입의 name이고, 나머지 인자는 그 타입의 옵션이에요.