typespec+ をリリースしました

type specifier(型指定子、以下 typespec)をもっと便利に使いたいなと思って。

何が問題なのか?

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

*1:lisp/condition.l にある xyzzy 標準の check-type では、いくつかの場合には predicate function の呼び出しに展開して typep の呼び出しを回避してる。