TYPECASE, CTYPECASE, ETYPECASE — 타입에 따라 분기하는 매크로
TYPECASE, CTYPECASE, ETYPECASE — 타입에 따라 분기하는 매크로
값의 타입에 따라 실행할 절을 고르는 조건 분기 매크로예요. 값의 동등성(equality)으로 나누는 case와 달리, 값이 어떤 타입(type-specifier)에 속하는지로 나눈다고 생각하면 돼요. 세 매크로는 어떤 절도 매치되지 않을 때의 동작만 달라져요.
시그니처
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-key—keyform또는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가 없으면typecase는nil을 돌려줘요. - 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)
ctypecase와 etypecase는 오류를 신호할 수 있으므로, 기존 핸들러들과 *debug-io*의 영향을 받을 수 있어요.
특이 상황 (Exceptional Situations)
ctypecase와 etypecase는 normal-clause가 하나도 매치되지 않으면 type-error 타입의 오류를 신호해요.
컴파일러는 어떤 절이 앞선 절들에 완전히 가려져 절대 선택되지 않을 때, style-warning 타입의 경고를 내보낼 수 있어요.
참고 (Notes)
(typecase test-key
{(type form*)}*)
==
(let ((#1=#:g0001 test-key))
(cond {((typep #1# 'type) form*)}*))
etypecase와 ctypecase가 쓰는 구체적인 오류 메시지는 구현마다 다를 수 있어요. 오류 메시지의 정확한 문구를 통제해야 하는 상황이라면, 적절한 메시지로 명시적으로 오류를 신호하는 otherwise-clause와 함께 typecase를 쓰는 게 더 나아요.
더 알아보기
case— 값의 동등성으로 분기하는 매크로cond— 조건 분기 매크로setf— 장소에 값을 넣는 매크로