#:g1: validate-superclassの謎

Posted 2021-03-22 01:47:43 GMT

MOPでメタクラスを定義した場合などに定義が必要になるvalidate-superclassですが、処理系によって定義が必要であったりなかったりするので、実際のところどういう動作が正しいのか改めて確認してみました。

メタクラス定義でvalidate-superclassを定義する意味

メタクラスが違う二つのクラスの間で継承関係が成立するかどうかは分からないのでデフォルトでは継承関係は成立しないとしていて、成立させたい場合は明示する仕組みというのが簡単な説明です。

この「デフォルトでは継承関係は成立しない」というのをvalidate-superclassで表現していて、成立させる場合にはTを返すメソッドを定義します。

(defclass my-class (standard-class)
  ())

(validate-superclass (class-prototype (find-class 'my-class)) (class-prototype (find-class 'standard-class))) → nil

この状態で、my-classをメタクラスとするクラスmy-objectを定義する場合、my-objectはオブジェクトの表現としてstandard-objectを継承して利用するのがデフォルト動作(省略時)なので、

(defclass my-object (standard-object)
  ()
  (:metaclass my-class))

のようなものを書いた場合、

(validate-superclass (class-prototype (find-class 'my-class))
                     (find-class 'standard-object))

のようなチェックが一連のスーパークラスで実施され、全てがTでなければ、エラーとなります。
処理系ごとのvalidate-superclassの動作の違いですが、下記のようになります。

明示的に指定しなければ互換性はないとする処理系

(validate-superclass (class-prototype (find-class 'my-class))
                     (find-class 'standard-object))
→ nil

AMOPに記載の通りの判定ですが、

あたりがそういう挙動で、validate-superclassをちゃんと書いてやる必要があります。

サブメタクラスがstandard-classのサブクラスで、スーパーメタクラスがstandard-classの場合は互換性あり

(validate-superclass (class-prototype (find-class 'my-class))
                     (find-class 'standard-object))
→ T

あたりがこの挙動です。
この挙動であれば、validate-superclassを書かなくて良さそうにも思えますが、メタクラスがstandard-classの別のサブクラス同士だと継承関係がない場合があるので、その場合はvalidate-superclassを書いてやる必要があります。

具体的には、下記のコードのような状況でvalidate-superclassの定義が必要になります。

(defclass my-class/ (standard-class)
  ())

(defclass my-object/ (standard-object) () (:metaclass my-class/))

(validate-superclass (class-prototype (find-class 'my-class/)) (find-class 'my-object)) → nil

(defmethod validate-superclass ((c my-class/) (s my-class)) T)

(validate-superclass (class-prototype (find-class 'my-class/)) (find-class 'my-object)) → T

;; 上記の定義がなければエラー (defclass my-object// (my-object) () (:metaclass my-class/))

サブメタクラス、スーパーメタクラスが共にstandard-classのサブクラスなら互換性あり

あたりがこの挙動です。
メタクラスがstandard-classのサブクラス同士であれば、validate-superclassの定義を書く必要はありません。
これはこれで便利な挙動で、validate-superclassの定義を書くことは殆ど無くなるのは良いのですが、この挙動が災いしてAllegro CLのコードの移植性の無さの一因になっている気がします。

上記のように処理系によってデフォルトの挙動が違いますが、互換性があることを明示するvalidate-superclassのコードがあっても挙動を変えることはないので、AMOP準拠で全部明示しておくのが吉かなと思います。

クラスに互換性がないとはどういうことか

ANSI CL規格では、互いに素である型が定義されていますが、

defclassdefine-conditiondefstructで継承関係を定義した型以外は互いに素であるとしています。
integerconsの間では継承関係を考えようとは思わないのですが、メタクラスをカスタマイズする場合は、メタクラスが異なるのみで他の挙動は継承したいことがほとんどかと思います。

validate-superclass の歴史

validate-superclassは用途が限定されている割には機能としては汎用的なのですが、もともとはcheck-super-metaclass-compatibilityという名前だったようです。
途中で、valid-superclass-p等の名前になったりもしたようですが、1990年頃、validate-superclassで落ち着き現在に至る様子。

check-super-metaclass-compatibilityvalidate-superclassよりも判定が厳しく、デフォルトの挙動は双方のメタクラスがeqの場合のみTとしていたようです。

現在のvalidate-superclasscheck-super-metaclass-compatibilityの目的に使うことが殆どですが、CMUCLやSBCLでは互換性の判定用に組み込みクラスについても非互換性のリストをもっているので、

(validate-superclass (find-class 'null)
                     (find-class 'cons))
→ nil

のように判定します。
他の処理系は、大体のところはstandard-classの範疇の判定しか想定していないようなのでTを返しますが、こんな動作でも問題ない程度には汎用的には使われていないということなのかもしれません……。

まとめ

validate-superclassについて掘り下げてみましたが、validate-superclassは用途が限定的ですし、考えるほどcheck-super-metaclass-compatibilityという名前のままでも良かったのではないかと思えてきます。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus