オブジェクト指向の難易度を下げる
Common Lispにはオブジェクト指向のシステムが存在します。
通称、CLOS (Common Lisp Object System)です。
本章は、不完全な知識でCLOSを開発していくことを考えます。
Common Lispでは、CLOSはオプションではなく必須の機能です。
特にエラーで使用します。
つまりcondition
です。
とにかくcondition
は早く必要な機能なのですけど、
CLOSの実装が意味不明過ぎてなかなか完成しませんでした。
何度も何度も作り直した記憶があります。
これがCommon Lispの開発の難易度を一気に上げていました。
でも分かってしまえば何のことはありません。
本投稿では最低限のCLOSのシステムを構築し、
早急に必要だったcondition
の判定を行う方法まで説明します。
1. オブジェクトとはkey-value構造体
「オブジェクト指向」とは全てを「オブジェクト」で作成するという
プログラミング言語の哲学のようなものです。
Common Lispはそもそもベースがオブジェクト指向ではないので、
全てがオブジェクトであるという考え方には完全に乗っていません。
でもCLOSというシステムだけ見ると、オブジェクト指向に従っていると言えます。
key-value構造体というのは、keyが与えられたら対応するvalueが返却されるというもの。
幸いにしてCommon Lispには、標準で3つものkey-value構造体が存在します。
- alist
- plist
hash-table
今回使うのは、alistです。
plistの方が好きっていうならplistでも構いません。
でもhash-table
はやめた方がいいでしょう。
「オブジェクト」というのは、別名インスタンスと呼ばれるものです。
Common Lispに限りませんが、インスタンスは大量に作成されます。
その一つ一つにhash-table
を割り当てるのは、
たぶん容量的に無駄が大きいですし、
しかもオブジェクト指向システムにおいて、そこまで求められていません。
だからhash-table
は、やりたいなら止めませんが、今回はやめておきます。
alist (associate list)というのは、こんな感じのものです。
((key1 . value1) (key2 . value2) ...)
plist (property list)というのあ、こんなレイアウト。
(key1 value1 key2 value2 ...)
見てわかる通り、非常に単純ですが、
データが大量にある場合は、検索に時間がかかるという欠点があります。
あと、key-valueのペアごとにcons
が1つ作成されるため、
効率もそれほど良くはありません。
まずは単純という利点を採用し、alistで説明して行きます。
2. CLOSオブジェクト
まずはオブジェクトを作りましょう。
オブジェクトは別名インスタンスです。
インスタンスは次の2つの情報を保有します。
class-of
- key-value構造体
class-of
というのは、そのインスタンスが一体誰から生まれたかという情報です。
Common Lispで書くならば、
(let ((inst (make-instance 'hello))) ...)
という文中において、inst
に束縛されるのがインスタンス、
inst
のclass-of
が、hello
というクラスのインスタンスになります。
それでは、CLOSオブジェクトのレイアウトを示します。
こんな感じにしようかと思います。
(clos-instance (key1 . value1) (key2 . value2) ...)
clos-instance
というシンボルはヘッダーです。
key1, value1, ...
はkey-value構造体です。
class-of
は一体どこに行ったのかというと、
keyがclos-class-of
というシンボルの場合を
専用に割り当てることにしました。
それでは、例としてhello
というクラスから作成され、
aaa
, bbb
というスロットを持つインスタンスのレイアウトを示します。
(clos-instance (clos-class-of . #<STANDARD-CLASS HELLO>) (aaa . clos-unbound) (bbb . clos-unbound))
非常に簡単ですね。
このインスタンスには3つのslotがあり、
名前はそれぞれclos-class-of
, aaa
, bbb
です。
clos-unbound
というのは、名の通りslotがunbound
であることを意味します。
この構造がオブジェクト指向の全ての基本となります。
もうこれだけで、クラスシステムだろうがジェネリック関数だろうが
なんだって構築できます。
では、インスタンスを作成する関数を作成します。
(defun clos-make (&rest args) (cons 'clos-instance (mapcar (lambda (x) (cons x 'clos-unbound)) (cons 'clos-class-of args)))) (defun clos-p (inst) (and (consp inst) (eq (car inst) 'clos-instance)))
clos-make
関数は、引数で指定したslotをもつインスタンスを作成します。
clos-p
は、CLOSオブジェクトかどうかを確認します。
例で示したインスタンスを作成するには次のように行います。
(clos-make 'aaa 'bbb)
これで、値が設定されていないインスタンスが作成されました。
ここからclos-class-of
というslotに値#<STANDADR-CLAS HELLO>
を
設定しなければなりませんが、設定の説明は省略し、
代わりにslotのアクセス関数について説明します。
3. slotの操作
slotの操作の基本となる関数を下記に示します。
(clos-get inst key) (clos-set inst key value) (clos-exists-p inst key) (clos-boundp inst key) (clos-makunbound inst key)
なぜCommon Lispの、例えばslot-value
を使用しないのでしょうか?
理由は、slot-value
などの標準関数はerror
conditionが発生するためです。
今回作成するのは、まだcondition
が作成されていないことを前提としています。
とはいっても、エラーは必ず発生するため、
Abortするだけの関数は用意しておこうと思います。
(defun clos-error (x &rest args) (format t "~&ERROR: ~S ~S~%" x args) (error 'error))
clos-error
関数は、エラーを通知します。
目的はシステムを異常終了させるだけなので、好きなように作ってよいと思います。
最後に(error 'error)
を実行していますが、適切なAbort関数に置き換えてください。
それではアクセス関数の実装を説明します。
(defun clos-cons (inst key) (unless (clos-p inst) (clos-error "type error." inst)) (assoc key (cdr inst))) (defun clos-get (inst key &optional (default nil default-p)) (let ((x (clos-cons inst key))) (cond (x (cdr x)) (default-p default) (t (clos-error "No slot name." key)))))
clos-get
関数は、slotから値を取得するためのものです。
slotが存在しない場合は、異常終了しますが、
第3引数を指定することで、デフォルト値を返却できます。
slot-value
であれば、slotが存在しない場合は
総称関数slot-missing
が呼ばれたり、
その結果、error
conditionが発生したりするのですが、
まだそんなシステムは存在しないため、
clos-error
関数を呼んで異常終了させています。
もしslotの値がunbound
の場合は、clos-unbound
というシンボルを返却します。
(defun clos-set (inst key value) (let ((x (clos-cons inst key))) (if x (rplacd x value) (clos-error "No slot name." key)) value))
clos-set
関数は、slotの値を設定します。
もしslotが存在しない場合は異常終了します。
(defun clos-exists-p (inst key) (and (clos-cons inst key) t))
clos-exists-p
関数はslotが存在するかどうかを調べます。
(defun clos-boundp (inst key) (not (eq (clos-get inst key) 'clos-unbound)))
clos-boundp
関数は、slotが値を持っているか、unbound
かどうかを調べます。
もしslotが存在しない場合は異常終了します。
(defun clos-makunbound (inst key) (clos-set inst key 'clos-unbound))
clos-makunbound
関数は、slotをunbound
にします。
もしslotが存在しない場合は異常終了します。
(defun clos-class-of (inst) (clos-get inst 'clos-class-of nil))
clos-class-of
関数は、class-of
を取得します。
clos-get
を使って直接値を取得しても良いのですが、
よく使うslotなので、専用の関数を用意しました。
5. CLOSオブジェクトの表示
これからCLOSオブジェクトを用いてクラスの作成をしていきますが、
クラスはslotに自分自身を格納したりしますので、
単純にformat
やらprin1
関数で表示しようとすると
循環が生じて非常に見づらいです。
pprint-dispatch
を操作して、
もう少し見やすいようにします。
(deftype clos () '(cons (eql clos-instance) list)) (defun clos-printer-instance (inst) (clos-get inst 'name "...")) (defun clos-printer-class-of (inst) (let ((x (clos-class-of inst))) (if x (clos-printer-instance x) "..."))) (defun clos-printer (stream inst) (format stream "#<CLOS: ~A ~A>" (clos-printer-class-of inst) (clos-printer-instance inst))) (set-pprint-dispatch 'clos #'clos-printer)
見づらくてもいいやって人は、pprint-dispatch
を操作する必要はありません。
6. クラスの作成
クラスとは、インスタンスを作成するための設計書です。
純粋なオブジェクト指向においては、
クラスもインスタンスであり、
他のインスタンスと同様にslotを持っています。
Common Lispでは、最低限次のようなslotが存在します。
name direct-superclasses direct-subclasses precedence-list direct-slots slots direct-initargs initargs finalized-p prototype
つまり、こんな感じで実行すれば、もうそれはクラスです。
(clos-make 'name 'direct-superclasses 'direct-subclasses 'precedence-list 'direct-slots 'slots 'direct-initargs 'initargs 'finalized-p 'prototype)
ここではもっと内容を詰めます。
とりあえず、クラスの作成は上記のように実行しますが、
使うslotは下記のものだけにします。
name
precedence-list
2つだけ!
他のやつらはどうしたんだということは、ちゃんと説明して行きます。
まずは次のslotから。
direct-superclasses
direct-subclasses
これらのslotの大きな仕事は、precedence-list
を構築することです。
まず、superclasses
が何かというと、
(defclass name (superclasses...) () ...)
上記で言う、スーパークラスを指定する場所です。
superclasses
のリストが指定されたら、
クラスを一つずつたどっていき、全てのクラスを寄せ集めて、
「トポロジカルソート」と呼ばれる並び替えを行います。
そうすることによって、最終的にクラスの順番が決定されて、
そのリストをprecedence-list
というslotに格納します。
precedence-list
というのは、つまりは優先順位リストです。
precedence-list
は色んなところで使います。
例えば、slotを作ったり、initargs
を作ったり、
あるいはmethod
の呼び出し順序を決めるときにも使います。
大抵の処理はprecedence-list
だけで何とかなります。
少なくとも、condition
を作成するまでは問題ありません。
subclasses
は、superclasses
の逆であり、
superclasses
で指定されたクラスのsubclasses
に、
defclass
で作成されたクラスが登録されます。
今回は全く使いません。
本来であればsuperclasses
からprecedence-list
を求めるのですが、
トポロジカルソートというものを作るのはそれなりに苦労します。
しかし、単純なものなら適当でもそれなりに動作しますし、
Common Lisp標準のクラスなら、規格書にprecedence-list
が載っていますので、
それを見て設定してあげるだけで問題ありません。
興味がある方は、ANSI Common Lispやらcltl2eやらに
詳しく載っているのでそちらをご覧ください。
では、話は変わって次のslotはどうなのか。
direct-slots
slots
これらのslotは、defclass
でいう、次の引数です。
(defclass name () (slots ...))
本来、defclass
は、これらのslotの情報を読み込んで、
適切なslotを生成するように計算しますが、
今は全部自分で計算してください。
つまり、clos-make
関数に指定するslotの名前を、
ちゃんと自分で手で書いてくださいということです。
それでは最後に、次のslotはどうでしょうか。
direct-initargs
initargs
これらは、make-instance
などで使われる、
&rest initarg &key &allow-other-keys
のことです。
初期化引数リストとも呼ばれます。
今はそもそもinitarg
を使いません。
つまり、make-instance
を使わず、
全てのインスタンスはclos-make
を使って自分で作成することを考えています。
以上をすべてまとめると、つまりは 下記の2つしかslotが必要ないということになります。
name
precedence-list
7. 標準クラスの作成
標準クラスというのは、具体的には下記のことです。
t
class
built-in-class
standard-class
standard-object
今回はstandard-class
とstandard-object
しかいりません。
しかし、これらの継承関係は複雑であり、
precedence-list
を示すと次のようになっています。
t -> t class -> class standard-object t built-in-class -> built-in-class class standard-object t standard-class -> standard-class class standard-object t standard-object -> standard-object t
さらに面白いことに、上記のclass-of
は次のようになります。
t -> built-in-class class -> standard-class built-in-class -> standard-class standard-class -> standard-class standard-object -> standard-class
standard-class
は、standard-class
から産まれたと言っています。
これがオブジェクト指向の面白い所であり、
純粋なオブジェクト指向なら、こういう親と子の閉ループが必ず生じます。
自分が自分を産んだって、細胞分裂でもしたんだろうか。
このような異常な関係は、Common Lispの標準関数では作成できません。
しかし、私たちにはclos-make
関数があります。
例え矛盾しまくっているような関係でも簡単に作成できます。
まずは、クラスを登録するシステムを作ります。
いわゆるfind-class
関数の別バージョンです。
クラスの登録は、symbol-plist
を利用することとします。
;; ;; clos-class-get -> (find-class 'name nil) ;; clos-class-set -> (setf (find-class 'name) value) ;; (defun clos-class-get (name &optional (errorp t)) (let ((x (get name 'clos-instance))) (cond ((clos-p x) x) (errorp (clos-error "clos-class-get error" name)) (t nil)))) (defun clos-class-set (name value) (setf (get name 'clos-instance) value))
clos-class-get
関数は、find-class
関数そのものです。
clos-class-set
関数は、クラスを登録するものです。
ではクラスを作成していきます。
まずは空のクラスを作成する関数を示します。
(defun clos-class-make () (clos-make 'name 'direct-superclasses 'direct-subclasses 'precedence-list 'direct-slots 'slots 'direct-initargs 'initargs 'finalized-p 'prototype)) (clos-class-set 't (clos-class-make)) (clos-class-set 'class (clos-class-make)) (clos-class-set 'built-in-class (clos-class-make)) (clos-class-set 'standard-class (clos-class-make)) (clos-class-set 'standard-object (clos-class-make))
空のクラスを先行して登録することで、
class-of
とprecedecen-list
に矛盾が生じたとしても
正しく処理できるようにします。
登録した後にクラスの情報を正しく設定します。
(defun clos-define-class (name args metaclass) (let* ((x (clos-class-get name)) (list (cons x (mapcar #'clos-class-get args))) (class-of (clos-class-get metaclass))) (clos-set x 'name name) (clos-set x 'precedence-list list) (clos-set x 'clos-class-of class-of))) (defmacro clos-class-list (name &rest args) `(clos-define-class ',name ',args 'standard-class)) (clos-define-class t nil 'built-in-class) (clos-class-list class standard-object t) (clos-class-list built-in-class class standard-object t) (clos-class-list standard-class class standard-object t) (clos-class-list standard-object t)
t
クラスだけ、class-of
がbuilt-in-class
なので処理が変わっています。
それでは、まずはclass-of
の確認から行いましょう。
確認は、単純にformat
で出力してみるだけです。
(format t "~S~%" (clos-class-get 't)) (format t "~S~%" (clos-class-get 'class)) (format t "~S~%" (clos-class-get 'built-in-class)) (format t "~S~%" (clos-class-get 'standard-class)) (format t "~S~%" (clos-class-get 'standard-object))
結果は下記の通り。
#<CLOS: BUILT-IN-CLASS T> #<CLOS: STANDARD-CLASS CLASS> #<CLOS: STANDARD-CLASS BUILT-IN-CLASS> #<CLOS: STANDARD-CLASS STANDARD-CLASS> #<CLOS: STANDARD-CLASS STANDARD-OBJECT>
続いてprecedence-list
の確認です。
(format t "~S~%" (clos-get (clos-class-get 't) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'class) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'built-in-class) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'standard-class) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'standard-object) 'precedence-list))
結果は下記の通り。
(#<CLOS: BUILT-IN-CLASS T>) (#<CLOS: STANDARD-CLASS CLASS> #<CLOS: STANDARD-CLASS STANDARD-OBJECT> #<CLOS: BUILT-IN-CLASS T>) (#<CLOS: STANDARD-CLASS BUILT-IN-CLASS> #<CLOS: STANDARD-CLASS CLASS> #<CLOS: STANDARD-CLASS STANDARD-OBJECT> #<CLOS: BUILT-IN-CLASS T>) (#<CLOS: STANDARD-CLASS STANDARD-CLASS> #<CLOS: STANDARD-CLASS CLASS> #<CLOS: STANDARD-CLASS STANDARD-OBJECT> #<CLOS: BUILT-IN-CLASS T>) (#<CLOS: STANDARD-CLASS STANDARD-OBJECT> #<CLOS: BUILT-IN-CLASS T>)
うまく登録されているのがわかります。
8. コンディションの作成
コンディションとは、エラーを扱った仕組みです。
condition
のクラスがエラーの種別であり、
condition
のインスタンスがエラーそのものになります。
本投稿の目的は、CLOSシステムを使用してcondition
の判定を行うことにありました。
例として下記のcondition
を作成することを考えます。
simple-error
program-error
どちらもprecedence-list
の内容はそれなりに複雑です。
;; class-precedence-list ;; -> condition t ;; -> serious-condition condition t ;; -> error serious-condition condition t ;; -> simple-condition condition t ;; -> simple-error simple-condition error serious-condition condition t ;; -> parse-error error serious-condition condition t
標準クラスの時と同様にclos-make
を使って生成して行けばいいだけです。
まずはcondition
クラスを作ります。
(clos-class-set 'condition (clos-class-make)) (clos-class-set 'serious-condition (clos-class-make)) (clos-class-set 'error (clos-class-make)) (clos-class-set 'simple-condition (clos-class-make)) (clos-class-set 'simple-error (clos-class-make)) (clos-class-set 'parse-error (clos-class-make)) (clos-class-list condition t) (clos-class-list serious-condition condition t) (clos-class-list error serious-condition condition t) (clos-class-list simple-condition condition t) (clos-class-list simple-error simple-condition error serious-condition condition t) (clos-class-list parse-error error serious-condition condition t)
では、simple-error
のインスタンスの作成を行います。
本来であればmake-instance
関数を使用するのですが、
先ほど説明した通り、今回はクラスのslotを
2つしか作成していないため使用することはできません。
作成する関数を下記に示します。
(defun make-instance-simple-error (control arguments) (let ((x (clos-make 'format-control 'format-arguments)) (class-of (clos-class-get 'simple-error))) (clos-set x 'format-control control) (clos-set x 'format-arguments arguments) (clos-set x 'clos-class-of class-of) x))
condition
の判定を行うためには、
CLOS専用のsubtypep
関数が必要となります。
ただ単純にprecedence-list
にクラスが存在するかどうかを確認するだけのものとなります。
確認に使用する関数を下記に示します。
(defun clos-subclass-p (inst super) (and (find super (clos-get inst 'precedence-list)) t)) (defun clos-subtype-p (inst super) (clos-subclass-p (clos-class-of inst) (clos-class-get super)))
それでは、インスタンスの確認を行ってみます。
(defun print-simple-error (x stream) (apply #'format stream (clos-get x 'format-control) (clos-get x 'format-arguments))) (let ((x (make-instance-simple-error "Hello: ~S~%" '(100)))) (format t "simple-condition check.~%") (when (clos-subtype-p x 'simple-condition) (print-simple-error x t)) (format t "parse-error check.~%") (when (clos-subtype-p x 'parse-error) (print-simple-error x t)))
実行結果は下記の通り。
simple-condition check. Hello: 100 parse-error check.
simple-condition
ではsubtypep
がt
となり、
program-error
ではnil
となったのがわかります。
9. まとめ
今回示したかったことは、CLOSシステムが不完全であっても、
単純にcons
の操作をするだけで、
クラスの判定をすることができるということです。
defclass
やdefgeneric
は、マクロを作るだけでも相当難しく、
実行する処理を完全に作るためには何日もかかる重労働となります。
だからこそ、本章で説明したような骨組みをあらかじめ作っておいて
後からdefclass
などを作るようにすれば、
手戻りはそれほど生じずに開発して行けるのだと思います。
A. 付録
最後に、実験に使用したコードを示します。
;; ;; clos ;; (defun clos-error (x &rest args) (format t "~&ERROR: ~S ~S~%" x args) (error 'error)) (defun clos-make (&rest args) (cons 'clos-instance (mapcar (lambda (x) (cons x 'clos-unbound)) (cons 'clos-class-of args)))) (defun clos-p (inst) (and (consp inst) (eq (car inst) 'clos-instance))) (defun clos-cons (inst key) (unless (clos-p inst) (clos-error "type error." inst)) (assoc key (cdr inst))) (defun clos-get (inst key &optional (default nil default-p)) (let ((x (clos-cons inst key))) (cond (x (cdr x)) (default-p default) (t (clos-error "No slot name." key))))) (defun clos-set (inst key value) (let ((x (clos-cons inst key))) (if x (rplacd x value) (clos-error "No slot name." key)) value)) (defun clos-exists-p (inst key) (and (clos-cons inst key) t)) (defun clos-boundp (inst key) (not (eq (clos-get inst key) 'clos-unbound))) (defun clos-makunbound (inst key) (clos-set inst key 'clos-unbound)) (defun clos-class-of (inst) (clos-get inst 'clos-class-of nil)) ;; ;; dispatch-printer ;; (deftype clos () '(cons (eql clos-instance) list)) (defun clos-printer-instance (inst) (clos-get inst 'name "...")) (defun clos-printer-class-of (inst) (let ((x (clos-class-of inst))) (if x (clos-printer-instance x) "..."))) (defun clos-printer (stream inst) (format stream "#<CLOS: ~A ~A>" (clos-printer-class-of inst) (clos-printer-instance inst))) (set-pprint-dispatch 'clos #'clos-printer) ;; ;; clos-class-get -> (find-class 'name nil) ;; clos-class-set -> (setf (find-class 'name) value) ;; (defun clos-class-get (name &optional (errorp t)) (let ((x (get name 'clos-instance))) (cond ((clos-p x) x) (errorp (clos-error "clos-class-get error" name)) (t nil)))) (defun clos-class-set (name value) (setf (get name 'clos-instance) value)) ;; ;; class ;;; (defun clos-class-make () (clos-make 'name 'direct-superclasses 'direct-subclasses 'precedence-list 'direct-slots 'slots 'direct-initargs 'initargs 'finalized-p 'prototype)) (defun clos-define-class (name args metaclass) (let* ((x (clos-class-get name)) (list (cons x (mapcar #'clos-class-get args))) (class-of (clos-class-get metaclass))) (clos-set x 'name name) (clos-set x 'precedence-list list) (clos-set x 'clos-class-of class-of))) (defmacro clos-class-list (name &rest args) `(clos-define-class ',name ',args 'standard-class)) (clos-class-set 't (clos-class-make)) (clos-class-set 'class (clos-class-make)) (clos-class-set 'built-in-class (clos-class-make)) (clos-class-set 'standard-class (clos-class-make)) (clos-class-set 'standard-object (clos-class-make)) (clos-define-class t nil 'built-in-class) (clos-class-list class standard-object t) (clos-class-list built-in-class class standard-object t) (clos-class-list standard-class class standard-object t) (clos-class-list standard-object t) (format t "~S~%" (clos-class-get 't)) (format t "~S~%" (clos-class-get 'class)) (format t "~S~%" (clos-class-get 'built-in-class)) (format t "~S~%" (clos-class-get 'standard-class)) (format t "~S~%" (clos-class-get 'standard-object)) (format t "~S~%" (clos-get (clos-class-get 't) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'class) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'built-in-class) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'standard-class) 'precedence-list)) (format t "~S~%" (clos-get (clos-class-get 'standard-object) 'precedence-list)) ;; ;; subtypep ;; (defun clos-subclass-p (inst super) (and (find super (clos-get inst 'precedence-list)) t)) (defun clos-subtype-p (inst super) (clos-subclass-p (clos-class-of inst) (clos-class-get super))) ;; ;; condition ;; (clos-class-set 'condition (clos-class-make)) (clos-class-set 'serious-condition (clos-class-make)) (clos-class-set 'error (clos-class-make)) (clos-class-set 'simple-condition (clos-class-make)) (clos-class-set 'simple-error (clos-class-make)) (clos-class-set 'parse-error (clos-class-make)) (clos-class-list condition t) (clos-class-list serious-condition condition t) (clos-class-list error serious-condition condition t) (clos-class-list simple-condition condition t) (clos-class-list simple-error simple-condition error serious-condition condition t) (clos-class-list parse-error error serious-condition condition t) (defun make-instance-simple-error (control arguments) (let ((x (clos-make 'format-control 'format-arguments)) (class-of (clos-class-get 'simple-error))) (clos-set x 'format-control control) (clos-set x 'format-arguments arguments) (clos-set x 'clos-class-of class-of) x)) (defun print-simple-error (x stream) (apply #'format stream (clos-get x 'format-control) (clos-get x 'format-arguments))) (let ((x (make-instance-simple-error "Hello: ~S~%" '(100)))) (format t "simple-condition check.~%") (when (clos-subtype-p x 'simple-condition) (print-simple-error x t)) (format t "parse-error check.~%") (when (clos-subtype-p x 'parse-error) (print-simple-error x t)))