DEFINE-CONDITION — 조건 타입 정의하기

DEFINE-CONDITION — 조건 타입 정의하기 (매크로)

condition type(에러·예외 객체)을 정의하는 매크로예요. 상위 타입(부모)으로부터 슬롯과 메서드를 상속받고, 슬롯과 리포팅 방식, 초기화 인자를 지정해서 Common Lisp의 조건 시스템을 구성해요.

출처: DEFINE-CONDITION - Common Lisp HyperSpec

시그니처 (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
  • namesymbol.
  • parent-typecondition type를 가리키는 symbol. parent-type이 제공되지 않으면 기본값은 (condition)이에요.
  • default-initargskeyword/value pairslist.
  • slot-specslotname이거나, 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-conditionname이라는 새 condition type을 정의하는데, 이는 parent-type이 가리키는 type들의 subtype이에요. 각 parent-type 인자는 새 condition의 direct supertype을 지정해요. 새 condition은 각 direct supertype으로부터 slotmethod를 상속받고, 그렇게 계속돼요.

슬롯 이름/슬롯 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을 제공해요. 이 formslot을 초기화하는 데 쓰일 때마다 평가돼요. 이 form이 평가되는 lexical environmentdefine-condition form이 평가된 lexical environment예요. 이 lexical environment는 변수와 function 둘 다를 가리킨다는 점을 유의하세요. local slot의 경우 dynamic environmentmake-condition이 호출된 dynamic environment이고, shared slot의 경우 dynamic environmentdefine-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 typeobject에 적용될 때 reader generic function의 결과 타입을 선언하는 셈이에요. slot의 타입을 만족하지 않는 값을 slot에 저장하려는 시도의 결과는 정의되지 않아요.

:default-initargs — 이 옵션은 defclass에서와 똑같이 취급돼요.

:documentation — :documentation 슬롯 옵션은 slot에 대한 documentation string을 제공해요.

:reportcondition 보고는 해당 condition typeprint-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에서 평가돼요. 이 두 인자(conditionstream)를 받는 function을 돌려줘야 하는데, 그 함수는 streamcondition의 설명을 출력해요. 이 functionprint-escapenil일 때 condition이 출력될 때마다 호출돼요.

report-namestring이면 다음의 축약이에요:

(lambda (condition stream)
  (declare (ignore condition))
  (write-string report-name stream))

이 옵션은 새 condition type이 정의된 후에 처리되므로, :report 함수 안에서 slot 접근자를 쓰는 것이 허용돼요. 이 옵션이 제공되지 않으면 이 condition type을 보고하는 방법에 대한 정보가 parent-type에서 상속돼요.

명시적으로 초기화되지도 않고 기본값도 주어지지 않은 slotread하려는 시도의 결과는 불특정(unspecified)이에요.

setfslot을 할당하려는 시도의 결과도 불특정이에요.

define-condition formtop level form으로 나타나면, compilername이 유효한 type 이름으로 인식되게 하고, 같은 file의 이후 define-condition form에서 이 condition type을 다른 condition typeparent-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

없음.