TYPECASE, CTYPECASE, ETYPECASE — 타입에 따라 분기하는 매크로

TYPECASE, CTYPECASE, ETYPECASE — 타입에 따라 분기하는 매크로

값의 타입에 따라 실행할 절을 고르는 조건 분기 매크로예요. 값의 동등성(equality)으로 나누는 case와 달리, 값이 어떤 타입(type-specifier)에 속하는지로 나눈다고 생각하면 돼요. 세 매크로는 어떤 절도 매치되지 않을 때의 동작만 달라져요.

출처: CLHS: Macro TYPECASE, CTYPECASE, ETYPECASE

시그니처

typecase keyform {normal-clause}* [otherwise-clause] => results*
ctypecase keyplace {normal-clause}* => results*
etypecase keyform {normal-clause}* => results*

normal-clause::= (type form*)
otherwise-clause::= ({otherwise | t} form*)
clause::= normal-clause | otherwise-clause

본문

인자와 값 (Arguments and Values)

  • keyform — 폼; 평가되어 test-key를 만들어요.
  • keyplace — 폼; 처음에 평가되어 test-key를 만들어요. 매치되는 타입이 없으면 나중에 place로 쓰일 수도 있어요.
  • test-keykeyform 또는 keyplace를 평가해 만든 객체.
  • type — 타입 지정자(type specifier).
  • forms — 암묵적 progn.
  • results — 매치된 절의 forms가 돌려준 값.

설명 (Description)

이 매크로들은 test-key를 타입 기준으로 매치해 선택된 절의 폼 몸체를 조건부 실행하게 해줘요.

keyform 또는 keyplace를 평가해 test-key를 만들어요.

그 다음 각 normal-clause를 차례로 고려해요. test-key가 그 절의 type에 속하면 그 절의 forms를 암묵적 progn으로 평가하고, 돌려준 값이 typecase, ctypecase, etypecase 폼의 값이 돼요.

이 세 매크로는 어떤 normal-clause도 매치되지 않을 때의 동작만 달라요. 구체적으로:

  • typecase — 매치되는 normal-clause가 없고 otherwise-clause가 있으면 그 절이 자동으로 매치돼요. otherwise-clause가 없으면 typecasenil을 돌려줘요.
  • ctypecase — 매치되는 normal-clause가 없으면 type-error 타입의 수정 가능한(correctable) 오류를 신호해요. 문제가 되는 데이터는 test-key이고, 기대되는 타입은 (or type1 type2 ...)과 동등한 타입이에요. store-value 리스타트로 오류를 수정할 수 있어요. store-value 리스타트가 호출되면 그 인자가 새 test-key가 되어 (setf keyplace test-key)처럼 keyplace에 저장돼요. 그런 다음 ctypecase는 각 절을 다시 고려하며 처음부터 시작해요. 대화형으로 호출되면 새 test-key를 입력받기 위해 사용자에게 프롬프트를 띄워요. keyplace의 하위 폼들은 어느 케이스도 성립하지 않으면 다시 평가될 수 있어요.
  • etypecase — 매치되는 normal-clause가 없으면 type-error 타입의 수정 불가능한(non-correctable) 오류를 신호해요. ctypecase와 달리 호출자는 normal-clause가 매치되지 않으면 etypecase가 돌아오지 않는다는 사실을 믿을 수 있어요.

세 경우 모두, 어느 하나가 다른 것의 하위 타입인 경우처럼 둘 이상의 절이 매치되는 타입을 지정해도 돼요. 그럴 때는 가장 먼저 적용되는 절이 선택돼요.

예제 (Examples)

;;; (Note that the parts of this example which use TYPE-OF
;;;  are implementation-dependent.)
 (defun what-is-it (x)
   (format t "~&~S is ~A.~%"
           x (typecase x
               (float "a float")
               (null "a symbol, boolean false, or the empty list")
               (list "a list")
               (t (format nil "a(n) ~(~A~)" (type-of x))))))
=>  WHAT-IS-IT
 (map 'nil #'what-is-it '(nil (a b) 7.0 7 box))
>>  NIL is a symbol, boolean false, or the empty list.
>>  (A B) is a list.
>>  7.0 is a float.
>>  7 is a(n) integer.
>>  BOX is a(n) symbol.
=>  NIL
 (setq x 1/3)
=>  1/3
 (ctypecase x
     (integer (* x 4))
     (symbol  (symbol-value x)))
>>  Error: The value of X, 1/3, is neither an integer nor a symbol.
>>  To continue, type :CONTINUE followed by an option number:
>>   1: Specify a value to use instead.
>>   2: Return to Lisp Toplevel.
>>  Debug> :CONTINUE 1
>>  Use value: 3.7
>>  Error: The value of X, 3.7, is neither an integer nor a symbol.
>>  To continue, type :CONTINUE followed by an option number:
>>   1: Specify a value to use instead.
>>   2: Return to Lisp Toplevel.
>>  Debug> :CONTINUE 1
>>  Use value: 12
=>  48
 x =>  12

예제의 ctypecase에서 값 1/3이 정수도 심볼도 아니어서 store-value 리스타트로 값을 고쳐 가며 12에서 48을 얻는 흐름을 볼 수 있어요.

영향 (Affected By)

ctypecaseetypecase는 오류를 신호할 수 있으므로, 기존 핸들러들과 *debug-io*의 영향을 받을 수 있어요.

특이 상황 (Exceptional Situations)

ctypecaseetypecasenormal-clause가 하나도 매치되지 않으면 type-error 타입의 오류를 신호해요.

컴파일러는 어떤 절이 앞선 절들에 완전히 가려져 절대 선택되지 않을 때, style-warning 타입의 경고를 내보낼 수 있어요.

참고 (Notes)

(typecase test-key
  {(type form*)}*)
==
(let ((#1=#:g0001 test-key))
  (cond {((typep #1# 'type) form*)}*))

etypecasectypecase가 쓰는 구체적인 오류 메시지는 구현마다 다를 수 있어요. 오류 메시지의 정확한 문구를 통제해야 하는 상황이라면, 적절한 메시지로 명시적으로 오류를 신호하는 otherwise-clause와 함께 typecase를 쓰는 게 더 나아요.

더 알아보기

  • case — 값의 동등성으로 분기하는 매크로
  • cond — 조건 분기 매크로
  • setf — 장소에 값을 넣는 매크로