DEFINE-CONDITION — 조건 타입 정의하기
DEFINE-CONDITION — 조건 타입 정의하기 (매크로)
새 condition type(에러·예외 객체)을 정의하는 매크로예요. 상위 타입(부모)으로부터 슬롯과 메서드를 상속받고, 슬롯과 리포팅 방식, 초기화 인자를 지정해서 Common Lisp의 조건 시스템을 구성해요.
시그니처 (Syntax)
define-condition name (parent-type*) ({slot-spec}*) option* => name
slot-spec ::= slot-name | (slot-name slot-option)
slot-option ::= [[{:reader symbol}* |
{:writer function-name}* |
{:accessor symbol}* |
{:allocation allocation-type} |
{:initarg symbol}* |
{:initform form} |
{:type type-specifier}]]
option ::= [[(:default-initargs . initarg-list) |
(:documentation string) |
(:report report-name)]]
function-name ::= {symbol | (setf symbol)}
allocation-type ::= :instance | :class
report-name ::= string | symbol | lambda expression
name—symbol.parent-type— condition type를 가리키는symbol. parent-type이 제공되지 않으면 기본값은(condition)이에요.default-initargs— keyword/value pairs의list.slot-spec— slot의 name이거나, slot-name 뒤에 0개 이상의 slot-option이 오는list.slot-name— 슬롯 이름(symbol), 슬롯 이름의list, 또는 슬롯 이름/슬롯 form 쌍의list.
slot-option
:reader— 주어진 slot에 대해 여러 번 제공될 수 있고 nil이 될 수 없어요.:writer— 주어진 slot에 대해 여러 번 제공될 수 있고 generic function을 가리켜야 해요.:accessor— 주어진 slot에 대해 여러 번 제공될 수 있고 nil이 될 수 없어요.:allocation— 주어진 slot에 대해 최대 한 번 제공될 수 있어요. 제공되지 않으면 기본값은 :instance예요.:initarg— 주어진 slot에 대해 여러 번 제공될 수 있어요.:initform— 주어진 slot에 대해 최대 한 번 제공될 수 있어요.:type— 주어진 slot에 대해 최대 한 번 제공될 수 있어요.:documentation— 주어진 slot에 대해 최대 한 번 제공될 수 있어요.:report— 최대 한 번 제공될 수 있어요.
본문 (Description)
define-condition은 name이라는 새 condition type을 정의하는데, 이는 parent-type이 가리키는 type들의 subtype이에요. 각 parent-type 인자는 새 condition의 direct supertype을 지정해요. 새 condition은 각 direct supertype으로부터 slot과 method를 상속받고, 그렇게 계속돼요.
슬롯 이름/슬롯 form 쌍이 제공되면, 슬롯 form은 make-condition이 명시적 값이 주어지지 않았을 때 기본값을 만들기 위해 평가할 수 있는 form이에요. 슬롯 form이 제공되지 않으면 slot의 내용은 implementation-dependent 방식으로 초기화돼요.
정의되는 type과 상속받는 다른 type이 같은 이름의 슬롯을 가지면, condition에는 슬롯이 하나만 할당돼요. 다만 제공된 슬롯 form이 parent-type에서 상속됐을 슬롯 form을 덮어써요. 슬롯 form이 제공되지 않으면 상속된 슬롯 form(있다면)이 여전히 보여요.
접근자(accessor)는 defclass가 쓰는 것과 같은 규칙으로 만들어져요.
slot-option 설명
:reader — :reader 슬롯 옵션은 :reader의 인자가 가리키는 generic function에 주어진 slot의 값을 읽는 unqualified method를 정의하도록 지정해요.
:initform — :initform 슬롯 옵션은 slot 초기화에 쓰일 기본 초기값 form을 제공해요. 이 form은 slot을 초기화하는 데 쓰일 때마다 평가돼요. 이 form이 평가되는 lexical environment는 define-condition form이 평가된 lexical environment예요. 이 lexical environment는 변수와 function 둘 다를 가리킨다는 점을 유의하세요. local slot의 경우 dynamic environment는 make-condition이 호출된 dynamic environment이고, shared slot의 경우 dynamic environment는 define-condition form이 평가된 dynamic environment예요.
어떤 구현도 (slot-name form)을 (slot-name :initform form)의 축약으로 허용하도록 define-condition의 문법을 확장할 수 없어요.
:initarg — :initarg 슬롯 옵션은 그 symbol 인자가 가리키는 초기화 인자를 선언하고, 이 초기화 인자가 주어진 slot을 초기화한다고 지정해요. initialize-instance 호출에서 그 초기화 인자에 값이 있으면 그 값이 주어진 slot에 저장되고, 슬롯의 :initform 슬롯 옵션(있다면)은 평가되지 않아요. 주어진 slot에 대해 지정된 초기화 인자 어느 것에도 값이 없으면, slot은 :initform 슬롯 옵션(지정된 경우)에 따라 초기화돼요.
:type — :type 슬롯 옵션은 slot의 내용이 항상 지정된 type임을 지정해요. 이는 이 condition type의 object에 적용될 때 reader generic function의 결과 타입을 선언하는 셈이에요. slot의 타입을 만족하지 않는 값을 slot에 저장하려는 시도의 결과는 정의되지 않아요.
:default-initargs — 이 옵션은 defclass에서와 똑같이 취급돼요.
:documentation — :documentation 슬롯 옵션은 slot에 대한 documentation string을 제공해요.
:report — condition 보고는 해당 condition type의 print-object method를 통해 중재되며, print-escape는 항상 nil이에요. condition type C의 정의에 (:report report-name)을 지정하는 것은 다음와 같아요:
(defmethod print-object ((x c) stream)
(if *print-escape* (call-next-method) (report-name x stream)))
:report(:report-name)의 인자가 제공하는 값이 symbol이나 lambda expression이면 function에 받아들여질 수 있어야 해요. (function report-name)은 현재 lexical environment에서 평가돼요. 이 두 인자(condition과 stream)를 받는 function을 돌려줘야 하는데, 그 함수는 stream에 condition의 설명을 출력해요. 이 function은 print-escape가 nil일 때 condition이 출력될 때마다 호출돼요.
report-name이 string이면 다음의 축약이에요:
(lambda (condition stream)
(declare (ignore condition))
(write-string report-name stream))
이 옵션은 새 condition type이 정의된 후에 처리되므로, :report 함수 안에서 slot 접근자를 쓰는 것이 허용돼요. 이 옵션이 제공되지 않으면 이 condition type을 보고하는 방법에 대한 정보가 parent-type에서 상속돼요.
명시적으로 초기화되지도 않고 기본값도 주어지지 않은 slot을 read하려는 시도의 결과는 불특정(unspecified)이에요.
setf로 slot을 할당하려는 시도의 결과도 불특정이에요.
define-condition form이 top level form으로 나타나면, compiler는 name이 유효한 type 이름으로 인식되게 하고, 같은 file의 이후 define-condition form에서 이 condition type을 다른 condition type의 parent-type으로 참조할 수 있어야 해요.
예제 (Examples)
다음 form은 blocks-world-error라는 condition type에서 상속받는 peg/hole-mismatch 타입의 condition을 정의해요:
(define-condition peg/hole-mismatch
(blocks-world-error)
((peg-shape :initarg :peg-shape
:reader peg/hole-mismatch-peg-shape)
(hole-shape :initarg :hole-shape
:reader peg/hole-mismatch-hole-shape))
(:report (lambda (condition stream)
(format stream "A ~A peg cannot go in a ~A hole."
(peg/hole-mismatch-peg-shape condition)
(peg/hole-mismatch-hole-shape condition)))))
새 타입은 peg-shape와 hole-shape 슬롯을 가지므로 make-condition이 :peg-shape와 :hole-shape 키워드를 받아들여요. readers인 peg/hole-mismatch-peg-shape와 peg/hole-mismatch-hole-shape는 :report 정보에 나타난 것처럼 이 타입의 객체에 적용돼요.
다음 form은 error에서 상속받는 machine-error라는 condition type을 정의해요:
(define-condition machine-error
(error)
((machine-name :initarg :machine-name
:reader machine-error-machine-name))
(:report (lambda (condition stream)
(format stream "There is a problem with ~A."
(machine-error-machine-name condition)))))
이 정의 위에, 머신을 사용할 수 없을 때 쓰기 위한 machine-error의 subtype인 새 에러 condition을 정의할 수 있어요:
(define-condition machine-not-available-error (machine-error) ()
(:report (lambda (condition stream)
(format stream "The machine ~A is not available."
(machine-error-machine-name condition)))))
이것은 machine-not-available-error 위에 세워진, 더 구체적인 condition이에요. machine-name에 대한 슬롯 초기화 form을 제공하지만 새 슬롯이나 report 정보는 제공하지 않아요. 그저 machine-name 슬롯에 기본 초기화를 줄 뿐이에요:
(define-condition my-favorite-machine-not-available-error
(machine-not-available-error)
((machine-name :initform "mc.lcs.mit.edu")))
:report 절이 없으므로, 이 condition 타입을 보고하는 데 machine-not-available-error에서 상속된 정보가 쓰인다는 점을 유의하세요.
(define-condition ate-too-much (error)
((person :initarg :person :reader ate-too-much-person)
(weight :initarg :weight :reader ate-too-much-weight)
(kind-of-food :initarg :kind-of-food
:reader :ate-too-much-kind-of-food)))
=> ATE-TOO-MUCH
(define-condition ate-too-much-ice-cream (ate-too-much)
((kind-of-food :initform 'ice-cream)
(flavor :initarg :flavor
:reader ate-too-much-ice-cream-flavor
:initform 'vanilla ))
(:report (lambda (condition stream)
(format stream "~A ate too much ~A ice-cream"
(ate-too-much-person condition)
(ate-too-much-ice-cream-flavor condition)))))
=> ATE-TOO-MUCH-ICE-CREAM
(make-condition 'ate-too-much-ice-cream
:person 'fred
:weight 300
:flavor 'chocolate)
=> #<ATE-TOO-MUCH-ICE-CREAM 32236101>
(format t "~A" *)
>> FRED ate too much CHOCOLATE ice-cream
=> NIL
더 알아보기 (See Also)
make-condition, defclass, Section 9.1 (Condition System Concepts)
Notes
없음.