subtypep実装の基本
正直、あっているかどうかわからないのですが、
確認のために記載していこうと思います。
今回の投稿だけでは終わらないので、気が向いたら続きを書きます。
subtypep
の使い方
subtypep
とは、左の型が右の型に含まれているかどうかを確認する関数です。
こんな感じで覚えておけばいいと思います。
(subtypep 小 大) のとき t
正確には等しい時もt
となります。
例をあげます。
;; integerはrealに含まれる。 (subtypep 'integer 'real) →t; t ;; stringはstringに含まれる(同じなので) (subtypep 'string 'string) →t; t
返却値は二つありますが、第二返却値がnil
の場合は
判断できなかったという意味になります。
型satisfies
を用いると、たぶん大体はnil; nil
となります。
(subtypep 'integer '(satisfies hello)) →nil; nil
実験では、hello
という関数が存在しなくてもnil; nil
が返却されました。
つまり関数を実行すらしていないということです。
なお、第二返却値がnil
の場合は、必ず第一返却値はnil
です。
t; nil
のような返却のパターンはありません。
よって、返却値は下記の3通りとなります。
第1 | 第2 | 意味 |
---|---|---|
t |
t |
含まれる |
nil |
t |
含まれない |
nil |
nil |
わからない |
subtypep
の実装はとても難しいです。
理由は、実数の範囲指定、and
、or
、not
があるからです。
範囲指定とは例えば次のようなことを言います。
(subtypep '(integer 10 20) '(real * 40)) →t; t
整数10~20
は、実数40
以下に含まれます、という回答になります。
and
, or
, not
はそのままなので、
あらためて説明する必要はないかと思います。
例えばこんな感じ。
(subtypep 'integer '(not character)) →t; t (subtypep '(integer 30 80) '(or (real 10 50) (real 40 100))) →t; t
範囲指定とand
, or
, not
を一気に説明するのは無理なので、
今回はnot
から説明していきます。
subtypep
のnot
型not
とは、その名の通り反対を意味します。
subtypep
にnot
が含まれる場合はどうなるかわかるでしょうか?
例えば下記の例を考えます。
(subtypep 'integer 'real) →t; t
含まれる場合は、not
にすると、返却値はたぶんnil
になるのではないでしょうか。
(subtypep '(not integer) 'real) →nil; t
あっていました。
じゃあ右をnot
にすると?
(subtypep 'integer '(not real)) →nil; t
こっちもあっていますね。
では次の例を考えましょう。
(subtypep 'string 'real) →nil; t
元々の返却値がnil
の場合は、否定したらt
になるのでしょうか?
(subtypep '(not string) 'real) →nil; t
なりませんでした。
でも右をnot
にするとt
になります。
(subtypep 'string '(not real)) →t; t
じゃあ、もともとnil
の場合は、右をnot
にすると返却値は必ずt
になるのか?
と言うと、そうでもありません。
(subtypep 'real 'integer) →nil; t (subtypep 'real '(not integer)) →nil; t
どういうこと?
これを理解するには、型の範囲をちゃんと考えなければいけません。
次の2例を考えます。
(subtypep 'string 'real) →nil; t (subtypep 'real 'integer) →nil; t
どちらも返却はnil
ですが、分けて考える必要があります。
上のstring
とreal
は、重複することが全くない、完全なる排他です。
しかし下のreal
とinteger
は、重複する部分があるものです。
両者が完全に排他の場合、右を否定した場合のみt
となります。
これは図にするとわかりやすいのではないでしょうか。
例えば、例として含まれる範囲を|****|
で表すとしましょう。
(subtypep 'string 'real) string: ------|****|---------------- real : -----------------|****|----- ★互いに疎で含まれない
分かってもらえるでしょうか。
横軸はてきとうです。
こんな感じだとして、|****|
の部分がお互いに含まれないと表現します。
real
のnot
を取ると次のようになります。
(subtypep 'string '(not real)) string : ------|****|---------------- (not real): *****************|----|***** ★|ここ|★が含まれる
こうすると、string
の|****|
の部分が、(not real)
に
完全に含まれています。
一方、real
とinteger
は次のようになります。
(subtypep 'real 'integer) real : ------|*****************|--- integer: -----------------|****|----- ★realの方が大きいのでnil
real
とinteger
は互いに疎ではなく、重複する部分があります。
よってnot
を取ると次のようになります。
(subtypep 'real '(not integer)) real : ------|*****************|--- (not integer): *****************|----|*****
(not integer)
はreal
を包括できていません。
こんなふうに、subtypep
では、nil
を返却する場合は、
排他的なのか、そうじゃないのかをちゃんと調べないと、
not
の場合に対応することができません。
subtypep
でnot
の有無を網羅すると、パターンは次の4通りになります。
(subtypep 'a 'b) (subtypep '(not a) 'b) (subtypep 'a '(not b)) (subtypep '(not a) '(not b))
このうちで、もともとnil
だったものがt
になるパターンは次の2通りです。
(subtypep 'a '(not b))
a
とb
が排他的の場合はt
(subtypep '(not a) '(not b))
a
がb
を含む場合はt
下の場合の、2つがどちらもnot
の判定は楽です。
a
とb
を逆にした(subtypep 'b 'a)
を調べればいいだけですから。
問題は排他的の場合です。
これは計算だったりアルゴリズムで算出できるものではなく、
実装する段階で、これこれのパターンは排他ですよという情報を
自分で返却するようにしなければなりません。
どうやって実装するのかというと、地道にif
文を並べて作りましょう。
subtypep
実装の基本の第一段階は、
not
, and
, or
が含まれない場合の関数subtypep!
を作成し、
次の4パターンを返却をさせることです。
(subtypep! 'a 'b) :true 含まれる t; tに対応 :false 含まれない nil; tに対応 :exclude 排他 nil; tに対応 :invalid わからない nil; nilに対応
では、:exclude
を考慮して、not
が現れたらt
とnil
を返却するような
subtypep!
を作ればいいのかと思うかもしれませんが、
それだと機能が足りません。
:exclude
を考慮して、:true
, :false
, :exclude
(と:invalid
)を返却するような
subtypep!
を作成するのです。
これらの値は、のちに作成するand
, or
でも使用します。
では、ひとまずand
, or
がない場合の
『not
ありsubtypep!
』のアルゴリズムを示します。
(subtypep! a b)
の場合 (★not
なしの場合)(subtypep! a b)
をそのまま返却
(subtypep! (not a) b)
の場合(subtypep! b a)
が (★逆です):true
なら:exclude
を返却:exclude
なら:false
を返却- その他ならそのまま返却
(subtypep! a (not b))
の場合(subtypep! a b)
が:true
なら:exclude
を返却:exclude
なら:true
を返却- その他ならそのまま返却
(subtypep! (not a) (not b))
の場合(subtypep! b a)
が (★逆です):true
なら:true
を返却:exclude
なら:false
を返却- その他ならそのまま返却
これでどうでしょうか。