typespec+ をリリースしました
type specifier(型指定子、以下 typespec)をもっと便利に使いたいなと思って。
- NetInsaller 用 カフェイン中毒
何が問題なのか?
typep が比較的遅いだとか、xyzzy だと deftype した型は si:canonicalize-type しないといけないだとか、そんなこんなで typespec はイマイチ使われてないけど、ちゃんと使えば便利なのではないかと。
typespec+ でできること #1: typep で si:canonicalize-type 不要
内容的には http://xyzzy.s53.xrea.com/wiki/index.php?patch%2Ftypespec.l と同様。
(deftype callable () `(or function (and symbol (satisfies fboundp)))) => callable (typep 'car 'callable) => t (typep #'car 'callable) => t
typespec+ でできること #2: typespec から型チェックする式を生成
typespec を利用するマクロ(check-type や typecase など)は、パフォーマンスを気にしないなら typep 呼び出しに展開すればいいのだけど
(defmacro my-check-type%0 (object typespec) `(let ((#1=#:object ,object)) (unless (typep #1# ',typespec) (error 'type-error :datum #1# :expected-type ',typespec)))) => my-check-type%0 (macroexpand-1 `(my-check-type%0 foo callable)) => (let ((#1=#:object foo)) (unless (typep #1# 'callable) (error 'type-error :datum #1# :expected-type 'callable))) => t
これだと、例えば typespec が string なら stringp で済むところを typep 呼び出すことになるので、無駄なことしてる気分になってしまう。*1
そんなワケで、typespec からできるだけストレートに型チェックする式を作る関数を作った。
(optimize-type-check 'x 'string) => (stringp x) (optimize-type-check 'x 'callable) => (or (functionp x) (and (symbolp x) (fboundp x)))
これを typep 呼び出しに展開してたところに埋め込めばいいので
(defmacro my-check-type%1 (object typespec) `(let ((#1=#:object ,object)) (unless ,(optimize-type-check '#1# typespec) (error 'type-error :datum #1# :expected-type ',typespec)))) => my-check-type%1 (macroexpand-1 `(my-check-type%1 foo callable)) => (let ((#1=#:object foo)) (unless (or (functionp #1#) (and (symbolp #1#) (fboundp #1#))) (error 'type-error :datum #1# :expected-type 'callable))) => t
typespec+ でできること #3: typespec から predicate function
「この型かどうか調べる関数」が欲しい場合(remove-if-not に渡す、など)にいちいち lambda 書かなくていいように。
(remove-if-not (typepred (or string symbol)) `(one :two "three" 4.0)) => (one :two "three") (every (typepred callable) (list #'car #'cdr 'cons)) => t