nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

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の実装はとても難しいです。
理由は、実数の範囲指定、andornotがあるからです。
範囲指定とは例えば次のようなことを言います。

(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から説明していきます。

subtypepnot

notとは、その名の通り反対を意味します。
subtypepnotが含まれる場合はどうなるかわかるでしょうか?

例えば下記の例を考えます。

(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ですが、分けて考える必要があります。
上のstringrealは、重複することが全くない、完全なる排他です。
しかし下のrealintegerは、重複する部分があるものです。

両者が完全に排他の場合、右を否定した場合のみtとなります。
これは図にするとわかりやすいのではないでしょうか。

例えば、例として含まれる範囲を|****|で表すとしましょう。

(subtypep 'string 'real)
  string:   ------|****|----------------
  real  :   -----------------|****|-----
                  ★互いに疎で含まれない

分かってもらえるでしょうか。
横軸はてきとうです。
こんな感じだとして、|****|の部分がお互いに含まれないと表現します。
realnotを取ると次のようになります。

(subtypep 'string '(not real))
  string    :   ------|****|----------------
  (not real):   *****************|----|*****
                    ★|ここ|★が含まれる

こうすると、string|****|の部分が、(not real)に 完全に含まれています。

一方、realintegerは次のようになります。

(subtypep 'real 'integer)
  real   :   ------|*****************|---
  integer:   -----------------|****|-----
                  ★realの方が大きいのでnil

realintegerは互いに疎ではなく、重複する部分があります。
よってnotを取ると次のようになります。

(subtypep 'real '(not integer))
  real         : ------|*****************|---
  (not integer): *****************|----|*****

(not integer)realを包括できていません。

こんなふうに、subtypepでは、nilを返却する場合は、 排他的なのか、そうじゃないのかをちゃんと調べないと、 notの場合に対応することができません。

subtypepnotの有無を網羅すると、パターンは次の4通りになります。

(subtypep 'a       'b)
(subtypep '(not a) 'b)
(subtypep 'a       '(not b))
(subtypep '(not a) '(not b))

このうちで、もともとnilだったものがtになるパターンは次の2通りです。

  • (subtypep 'a '(not b))
    • abが排他的の場合はt
  • (subtypep '(not a) '(not b))
    • abを含む場合はt

下の場合の、2つがどちらもnotの判定は楽です。
abを逆にした(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が現れたらtnilを返却するような 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を返却
      • その他ならそのまま返却

これでどうでしょうか。