nptclのブログ

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

7章Objectsのクラス関連の和訳

【追記】7章Objectsの全てを日本語訳しました。

https://nptcl.github.io/npt-japanese/docs/ansicl/7.html
https://nptcl.github.io/npt-japanese/md/ansicl/7.html

ふたつのリンクは、レイアウトが違うだけでどちらも同じ内容なので好きな方を見てください。
リンク先はDictionaryも翻訳してます。
ただし翻訳してあるのは7章だけです。

ANSI Common Lispの規格書の、「7. Objects」の一部の和訳です。
必要になったので必死に翻訳しました。
訳があっているかどうかは知らん。

翻訳元は下記の通り。

draft proposed American National Standard for Information Systems
Programming Language Common Lisp
Version 15.17R, X3J13/94-101R.
Fri 12-Aug-1994 6:35pm EDT
http://www.cs.cmu.edu/afs/cs/Web/Groups/AI/lang/lisp/doc/standard/ansi/dpans/

HyperSpecだと下記の通り。

7. Objects
http://www.lispworks.com/documentation/HyperSpec/Body/07_.htm

7.6 Generic Functions and Methods以降は長いのでまた今度。

【追記】続きを投稿しました。7章Objectsのジェネリック関数関連の和訳

目次

7. オブジェクト
7.1 オブジェクトの作成と初期化
7.1.1 初期化引数
7.1.2 初期化引数の有効性の宣言
7.1.3 初期化引数のデフォルト値
7.1.4 初期化引数の規則
7.1.5 Shared-Initialize
7.1.6 Initialize-Instance
7.1.7 Make-InstanceとInitialize-Instanceの宣言
7.2 インスタンスのクラスの変更
7.2.1 インスタンスの構造の修正
7.2.2 新しく追加された局所スロットの初期化
7.2.3 インスタンスのクラスの更新のカスタマイズ
7.3 インスタンスの再初期化
7.3.1 再初期化のカスタマイズ
7.4 メタオブジェクト
7.4.1 標準メタオブジェクト
7.5 スロット
7.5.1 スロットの紹介
7.5.2 スロットへのアクセス
7.5.3 スロットの継承とスロットオプション
【別投稿】7.6 ジェネリック関数とメソッド

7. オブジェクト

7.1 オブジェクトの作成と初期化

ジェネリック関数make-instanceは、クラスの新しいインスタンスを作成し返却します。 最初の引数はクラスか、クラスの名前であり、残りの引数は初期化引数リストです。

新しいインスタンスの初期化は、いくつかのステップから成ります。 内容は次のようになります。 指定されなかった初期化引数の値に対して、明に指定された初期化引数とデフォルト値を結びつけるステップ。 初期化引数の有効性をチェックするステップ。 インスタンスの記憶領域を確保するステップ。 スロットに値を埋めるステップ。 そして追加の初期化を行うためにユーザーが提供したメソッドを実行するステップ。 make-instanceの各ステップはジェネリック関数により実装されているため、 それぞれのステップをカスタマイズする仕組みが提供されています。 加えて、make-instance自体がジェネリック関数であるため、自身もカスタマイズできます。

オブジェクトシステムは各ステップに対して、システムで提供されたメソッドを用意しています。 メソッドは初期化全体の手順の標準的な振る舞いを定義したものです。 標準の振る舞いは、下記の4つの簡単な仕組みによって、初期化を制御することができます。

  • スロットの初期化引数としてのシンボルの宣言。 初期化引数はdefclassのスロットオプションである、 :initargを使うことで宣言できます。 これは、make-instanceの呼び出し時に、 スロットの値を設定するための仕組みとして提供されたものです。

  • 初期化引数のデフォルト値フォームの指定。初期化引数のデフォルト値フォームは、 defclassのクラスオプションである:default-initargsを使うことで定義できます。 もし初期化引数がmake-instanceの引数として明に提供されなかった場合、 デフォルト値フォームはdefclassが宣言されたレキシカル環境の中で評価されます。 そして評価された結果の値は、初期化引数の値として使用されます。

  • スロットのデフォルト初期化値フォームの提供。スロットのデフォルト初期化値フォームは、 defclassのスロットオプション:initformを使うことで宣言されます。 もしmake-instanceの引数かあるいは:default-initargsのデフォルト値にて、 スロットに対応する初期化引数が与えられていなかった場合、 デフォルト値フォームはdefclassが宣言されたレキシカル環境の中で評価されます。 そして評価された結果の値はスロットに格納されます。 局所スロットの:initformフォームは、インスタンスが作成されたとき、 クラスの再定義によりインスタンスを更新するとき、 そしてインスタンスを違うクラスの定義に更新するときに使用されるでしょう。 共有スロットの:initformフォームに関しては、 定義のときか、再定義のときに使用されます。

  • initialize-instanceshared-initializeのメソッド定義。 スロットの値を設定するこれらの振る舞いは、 システムが提供するメソッドで提供されており、 initialize-instanceは、shared-initilizeを呼び出すように実装されています。 ジェネリック関数shared-initializeは初期化の部分を実装しており、 次の4つの状況で共有されています。 それは、インスタンス作成時、インスタンスの再初期化時、 クラスの再定義によるインスタンスの更新時、 そして違うクラス定義へインスタンスを更新するときです。 システムが提供するshared-initializeのメソッドは、 スロットの値を更新するための上記の振る舞いを直接実装しているため、 initialize-instanceは単純にshared-initializeを呼び出すだけとなります。

7.1.1 初期化引数

初期化引数は、オブジェクトの作成と初期化を制御します。 よくキーワードを初期化引数の名前にするのが便利で使われますが、 初期化引数の名前はnilを含むどんなシンボルでも使用できます。 初期化引数は、次の2つの方法である、 スロットの値を埋めるためか、 あるいは初期化メソッドの引数に提供するときに使用します。 単一の初期化引数は、両方の目的で使用されます。

初期化引数リストは、初期化引数の名前と値のプロパティリストです。 この構造は、通常のプロパティリストとして同一であり、 引数リストの&keyパラメーターとして処理される部分としても同一です。 これらのリストは、もし初期化引数の名前が初期化引数リストに複数現れた場合は、 もっとも左側に現れた値が指定され、残りのものは無視されます。 make-instanceの引数(最初の引数よりあとのもの)の形は、初期化引数リストです。

初期化引数はスロットと結び付けることができます。 もし初期化引数が初期化引数リストの中で値を持っている場合、 その値は新しく作成されたオブジェクトのスロットに格納されます。 もし:initformフォームがスロットと結び付けられていた場合でも、初期化引数の方が上書きをします。 1つの初期化引数は、複数のスロットを初期化することができます。 共有スロットを初期化する初期化引数は、以前の値を置き換えて、共有スロットに値を格納します。

初期化引数はメソッドに結び付けることができます。 オブジェクトが作成されて、特定の初期化引数が与えられた場合、 ジェネリック関数であるinitialize-instance, shared-initialize, そしてallocate-instanceは、 キーワード引数のペアとして、初期化引数の名前と値とともに呼び出されます。 もし初期化引数の値が初期化引数リストで提供されていなかった場合は、 メソッドのラムダリストがデフォルト値を提供します。

初期化引数は次の4つの状況によって使用されます。 インスタンスの作成時、インスタンスの再初期化時、 クラス再定義によるインスタンスの更新時、そして違うクラス定義へのインスタンスを更新するときです。

初期化引数は特定のクラスのインスタンスの作成と初期化時に制御で使用されるため、 初期化引数は、クラスの「初期化引数は〜」のように記述します。

7.1.2 初期化引数の有効性の宣言

初期化引数は、4つの状況にて有効性がチェックされます。 初期化引数はひとつの状況では有効かもしれませんが、他はそうではないかもしれません。 例えば、システムで提供されたmake-instanceのメソッドのstandard-classクラスを対象とした場合を考えます。 もし初期化引数が与えられていたものの、有効性としての宣言がされていなかった場合、 メソッドは初期化引数の有効性チェックにおいてエラーのシグナルを発することになります。

初期化引数の有効性の宣言は、次の2つの意味があります。

  • スロットを設定するときの初期化引数は、 defclassのスロットオプションである:initargによって有効であるとして宣言されます。 スロットオプションの:initargは、スーパークラスから継承されます。 よって、クラスのスロット設定時の有効な初期化引数の集合は、 クラスとスーパークラスによって有効であると宣言されたスロット設定時の初期化引数の和集合となります。 初期化引数による値の設定は、4つの状況すべてにおいて有効です。

  • メソッドの引数として与えられた初期化引数は、 これらメソッドの宣言によって有効であると定義されます。 メソッドのラムダリストとして定義されたキーワードパラメーターのキーワード名は、 全てのクラスの適用可能なメソッドの初期化引数となります。 適用されるメソッドのラムダリストにある&allow-other-keysの存在は、 初期化引数の有効性のチェックを無効にします。 よってメソッドの継承は、メソッドに引数として渡される有効な初期化引数の集合を制御します。 メソッドの定義を持ったジェネリック関数は、下記に示すものとして、 有効な初期化引数の宣言を守ります。

    • 関数allocation-instance, initialize-instanceshared-initializeにより、 クラスのインスタンスを作成するとき。 クラスのインスタンスを作成するとき、 これらのメソッドにより有効だと宣言された初期化引数は有効です。

    • 関数reinitialize-instanceshared-initializeにより、インスタンスの再初期化が行われるとき。 インスタンスの再初期化が行われるとき、 これらのメソッドにより有効だと宣言された初期化引数は有効です。

    • 関数update-instance-for-redefined-classshared-initializeにより、 再定義されたクラスにインスタンスを更新するとき。 再定義されたクラスにインスタンスを更新するとき、 これらのメソッドにより有効だと宣言された初期化引数は有効です。

    • 関数update-instance-for-different-classshared-initializeにより、 違うクラスの定義にインスタンスを更新するとき。 違うクラスの定義にインスタンスを更新するとき、 これらのメソッドにより有効だと宣言された初期化引数は有効です。

クラスの有効な初期化引数の集合は、スロットの値の設定か、 あるいは初期化引数の前宣言として与えられる:allow-other-keysに従った メソッドの引数かのどちらかの初期化引数の集合です。 :allow-other-keysのデフォルト値はnilです。 もし初期化引数:allow-other-keysの値がtrueであるならば、 初期化引数の有効性の確認は無効となります。

7.1.3 初期化引数のデフォルト値

クラスオプションである:default-initargsを使うことで、 初期化引数のデフォルト値フォームを提供することができます。 もしいくつかのクラスによって初期化引数が有効であると宣言された場合は、 デフォルト値フォームは違うクラスによって設定されるかもしれません。 このような場合では、:default-initargsは継承された初期化引数によって提供されたデフォルト値が使用されます。

オプション:default-initargsは、初期化引数へのデフォルト値の提供のみに使用されます。 このオプションでは、シンボルを有効な初期化引数の名前として宣言しません。 さらに、オプション:default-initargsは、 インスタンス作成時における初期化引数のデフォルト値の提供としてのみ使用されます。

クラスオプションの引数である:default-initargsは、 初期化引数の名前とフォームが交互に現れるリストです。 各フォームは、初期化引数に対応するデフォルト値のフォームです。 初期化引数のデフォルト値のフォームは、 make-instanceの引数に初期化引数として現れていなかった場合、 かつ、もっと特定的なクラスによってデフォルト値が定義されていなかった場合のみに、 評価されて使用されます。 デフォルト値のフォームは、defclassフォームのレキシカル環境で評価されたものが提供され、 評価された結果は初期化引数の値として使用されます。

make-instanceに指定された初期化引数は、 デフォルトの初期化引数と結び付けられ、 デフォルト初期化引数リストを生成します。 デフォルト初期化引数は、初期化引数の名前と値を交互にリストにしたものです。 このリストは、指定されていない初期化引数のデフォルト値を決定するものであり、 また明示的に初期化引数が指定されたものは、 デフォルト初期化引数リストのより早く表れたもののリストとします。 デフォルト初期化引数は、クラス優先リストの順番に従ったクラスのデフォルト値に順番付されます。

:default-initargs:initformでは、どちらもスロットの初期化に使用されますが、 両者の間には目的に違いがあります。 クラスオプションである:default-initargsは、ユーザーに対して、 初期化引数がスロットを初期化されているかどうか、 あるいはメソッドに渡されるかどうかを知ることなしに、デフォルト値を与える仕組みを提供します。 もしmake-instanceを呼ぶ際に、初期化引数を明示的に与えなかった場合はデフォルト値が使用されますが、 デフォルト値は呼び出し時に指定されたものとして呼び出されます。 対称的に、スロットオプションである:initformは、 ユーザーがスロットのデフォルト値フォームを与えるための仕組みとして提供されます。 :initformフォームはスロットの初期化に使用されますが、 ただmake-instanceに与えられた初期化引数に対応するスロットとの結びつきがなかった場合、 あるいは:default-initargsにデフォルト値の指定がなかった場合のみ、:initformにて初期化が行われます。

初期化引数のデフォルト値フォームの評価順序と、 :initformフォームの評価順序は定義されてはいません。 もし評価順序が重要である場合は、 代わりにinitialize-instanceshared-initializeメソッドを使用してください。

7.1.4 初期化引数の規則

スロットオプションの:initargは、スロット対して複数定義されるかもしれません。

もし初期化引数に複数の定義がされるかもしれないときには、下記に示すルールが適応されます。

  • もし同じ初期化引数の名前が:initargスロットオプションに複数現れた場合は、 初期化引数は複数のスロットを初期化できます。

  • 初期化引数の名前は、複数の初期化メソッドのラムダリストに現れます。

  • 初期化引数の名前は、スロットオプションの:initargと、初期化メソッドのラムダリストの両方に現れます。

もしmake-instanceに与えられた引数が、同じスロットを初期化するような複数の初期化引数であった場合、 さらに初期化引数が違った名前であったときは、初期化引数リストの最も左の初期化引数の値が採用されます。

もし複数の違った初期化引数が同じスロットを初期化する場合、 さらにスロットはデフォルト値を持っており、 make-instanceの引数には明示的に指定されていなかったときは、 初期化引数は最も特定的なクラスのクラスオプション:default-initargsに現れる値が採用されます。 もしひとつの:default-initargsクラスオプションが、 複数の初期化引数により同じスロットを初期化する場合、 さらにmake-instanceの引数には明示的に指定がなかったときは、 クラスオプション:default-initargsの最も左側の値が採用され、 残りのデフォルト値フォームの値は無視されます。

make-instanceの引数として明示的に与えられた初期化引数は、 デフォルト初期化引数の左側に現れます。 例えば、クラスC1C2が違うスロットに対してデフォルト初期化引数の値を与えた場合を考えます。 C1C2よりも特定的であるとします。 C1によって提供されたデフォルト初期化引数は、 デフォルト初期化引数リストにおいては、 C2によって提供されたのデフォルト初期化引数の左側に位置します。 もし単一のクラスオプション:default-initargsが、 2つの違ったスロットに対して初期化引数の値が与えられた場合、 クラスオプション:default-initargsの最も左に位置する初期化引数が、 デフォルト初期化引数リストの最も左側に現れます。

もしスロットが:initformフォームと:initargスロットオプションの両方を持っており、 さらに初期化引数が:default-initargsによるデフォルト値により与えられているか、 あるいはmake-instanceの引数により与えられていた場合、 :initformフォームは使われませんし評価もされません。

上記の規則の例を示します。

(defclass q () ((x :initarg a)))
(defclass r (q) ((x :initarg b))
  (:default-initargs a 1 b 2))
フォーム                      デフォルト初期化引数リスト    スロットXの値
----------
(make-instance 'r)            (a 1 b 2)                     1
(make-instance 'r 'a 3)       (a 3 b 2)                     3
(make-instance 'r 'b 4)       (b 4 a 1)                     4
(make-instance 'r 'a 1 'a 2)  (a 1 a 2 b 2)                 1

7.1.5 Shared-Initialize

ジェネリック関数shared-initializeは、 インスタンスの作成時、インスタンスの再初期化時、 クラス再定義によるインスタンス更新時、違うクラスへのインスタンス更新時において、 インスタンスのスロット値を、初期化引数か:initformフォームによって設定する際に使用されます。 method-combinationはstandardが使用されます。 引数は次のような順番で受け取ります。 初期化されるインスタンスインスタンスのアクセス可能なスロット名の集合、 そして任意の長さの初期化引数です。 最初2つよりあとの引数は、初期化引数リストの形にしなければなりません。

shared-initializeの2番目の引数は、下記のどちらかに従います。

  • 引数はスロットの名前のリスト(空リストでも可)であり、 スロット名の集合を指定したものです。

  • 引数はシンボルtであり、すべてのスロットの集合を指定したものです。

システムが提供しているshared-initializeのメソッドでは、 第一引数の特定パラメーターがstandard-objectクラスのものが存在します。 このメソッドは共有か局所かに関わらず、各スロットに対して次の振る舞いを行います。

  • もし初期化引数リスト中の初期化引数がスロットへの値を特定した場合は、 メソッドが実行する前に対象のスロットにすでに値が格納されていても、 スロットへ特定した値が格納されます。 影響があるスロットは、shared-initializeの第二引数で指定されたスロットとは独立しています。

  • 第二引数によって指定されたどんなスロットも、 この時点においてまだunboundであった場合は、 :initformフォームに従って初期化されます。 :initformフォームを持つどのスロットも、 フォームはdefclass宣言のレキシカル環境にて評価され、 その結果がスロットに格納されます。 例えば、beforeメソッドがスロットへ値を格納する場合、 :initformフォームはスロットへの値の格納には使用されないでしょう。 もし第二引数が指定した名前が、 インスタンスのアクセス可能なスロットに対応していなかった場合は、 結果は定義されていません。

  • この規則は7.1.4 初期化引数に従います。

ジェネリック関数shared-initializeは、 システムが提供するメソッドreinitialize-instance, update-instance-for-different-class, update-instance-for-redefined-class, そしてinitialize-instanceによって呼び出されます。 このようにメソッドは これらすべてのコンテキスト上で実行できるようなアクションを指定するように、 shared-initializeを記述することができます。

7.1.6 Initialize-Instance

ジェネリック関数initialize-instanceは、 新しく作成されたインスタンスを初期化するために、 make-instanceによって呼び出されます。 method-combinationはstandardが使われます。 initialize-instanceのメソッドは、 単純に初期値をスロットに指定できないような初期化を実行するために定義できます。

初期化中では、次に示したアクションを実行したあとにinitialize-instanceが呼び出されます。

  • デフォルト初期化引数リストは、 提供された初期化引数リストと各クラスのデフォルト初期化引数を結びつける計算がされます。

  • デフォルト初期化引数リストの有効性はチェックされます。 もしどの初期化引数の有効として宣言されていなかった場合は、エラーが発せられます。

  • 新しいインスタンスはスロットがunboundとして作成されます。

ジェネリック関数initialize-instanceは 新しいインスタンスとデフォルト初期化引数とともに呼び出されます。 システムが提供するinitialize-instanceのメソッドでは、 特定パラメーターはstandard-objectクラスのものが存在します。 このメソッドは、ジェネリック関数shared-initializeを呼び出し、 初期化引数に対応したものか、 あるいは:initformフォームに対応した値を設定します。 ジェネリック関数shared-initializeの引数は、 インスタンスt、デフォルト初期化引数を指定して呼び出されます。

注意として、initialize-instanceはデフォルト初期化引数リストを shared-initializeの呼び出し時に提供します。 そして最初のステップとして、 システムが提供するshared-initializeのメソッドは、 make-instance呼び出し時に提供された初期化引数と、 デフォルト初期化引数リストの両方を集計して呼び出されます。

initialize-instanceのメソッドは、 インスタンスの初期化時に、特定のアクションを定義することができます。 もしinitialize-instanceafterメソッドだけが定義された場合、 これらはシステムが提供した初期化後に実行されます。 したがってこれらは、initialize-instanceの標準的な動作には干渉しないでしょう。

オブジェクトシステムは、initialize-instanceメソッドの構築に便利な2つの関数を提供しています。 関数slot-boundpは、スロットが値を持っているかどうかを示すbool値を返却します。 これはinstance-initializeafterメソッドを記述する際に、 まだ初期化されていないスロットのみを初期化するような仕組みを提供します。 関数slot-makunboundは、スロットの値を削除します。

7.1.7 Make-InstanceとInitialize-Instanceの宣言

ジェネリック関数make-instanceは、最適化を考えない場合は、 下記に示す宣言のように実行されます。

(defmethod make-instance ((class standard-class) &rest initargs)
  ...
  (let ((instance (apply #'allocate-instance class initargs)))
    (apply #'initialize-instance instance initargs)
    instance))

(defmethod make-instance ((class-name symbol) &rest initargs)
  (apply #'make-instance (find-class class-name) initargs))

make-instanceの定義で省かれているコードは、 initargsをデフォルト初期化引数によって指定する部分であり、 また初期化引数の結果を初期化引数に設定するかどうか決定するために、 スロットに値が設定されておらず、 メソッドの引数として供給もされていないかどうかをチェックする部分となります。

ジェネリック関数initialize-instanceは、最適化を考えない場合は、 下記に示す宣言のように実行されます。

(defmethod initialize-instance ((instance standard-object) &rest initargs)
  (apply #'shared-initialize instance t initargs)))

これらのコードはカスタマイズ可能です。

プログラマーへのインターフェイスレベルとしてカスタマイズできるものは、 defclassのオプションである、:initform, :initargそして:default-initargsが含まれますし、 同様にmake-instance, allocate-instance, そしてinitialize-instanceのメソッド宣言があげられます。 shared-initializeのメソッドを定義することも可能です。 この関数は、ジェネリック関数のreinitialize-instance, update-instance-for-redefined-class, update-instance-for-defferent-class, そしてinitialize-instanceによって実行されます。 メタオブジェクトレベルでは、追加でカスタマイズをサポートします。

処理系は、initialize-instanceshared-initializeについて明確な最適化を許容しています。 7章にあるshared-initializeの定義では、可能な最適化についての説明があります。

7.2 インスタンスのクラスの変更

関数change-classは、 インスタンスのクラスを現在のクラスCfromから違うクラスCtoへ変更する際に使用します。 この関数はインスタンスの構造を変化させて、 クラスCtoの定義へ適用させるような変更を行います。

インスタンスのクラスの変更は、 スロットへの追加と削除が行われるでしょう。 インスタンスのクラスの変更は、関数eqにより定義されたものとの同一性を変更しません。

関数change-classインスタンスに対して実行されたとき、 2つの手順により更新が行われます。 最初の手順は、新しい局所スロットの追加と、 新しいバージョンには定義されない局所スロットの削除によるインスタンスの構造の変更です。 二番目の手順は、新しく追加された局所スロットの初期化と、 他にユーザーが定義したアクションの実行です。 これら2つの手順は、次に示す2つの章によって定義されています。

7.2.1 インスタンスの構造の修正

インスタンスをクラスCtoへ修正するために、 クラスCtoには定義されているがCfromには定義されていない局所スロットは追加され、 クラスCtoには定義されていないがCfromには定義されている局所スロットは削除されます。

クラスCtoとクラスCfromの両方に定義されている局所スロットの値は保持されます。 もし局所スロットがunboundであった場合は、unboundのままです。

クラスCfromでは共有スロットであり、Ctoでは局所スロットとして定義された スロットの値は保持されます。

最初の更新ステップでは、どの共有スロットの値も影響がありません。

7.2.2 新しく追加された局所スロットの初期化

更新の二番目の手順では、新しく追加されたスロットを初期化し、 ユーザー定義のアクションを実行します。 このステップは、ジェネリック関数update-instance-for-different-classによって定義されます。 ジェネリック関数update-instance-for-different-classは、 最初の更新手順が完了したあとに、change-classによって実行されます。

ジェネリック関数update-instance-for-different-classは、 change-classによって計算された引数により実行されます。 最初の引数は、更新されるインスタンスのコピーであり、 クラスCfromのインスタンスです。 このコピーは、ジェネリック関数change-classに動的エクステントとして保有されます。 二番目の引数は、change-classによって更新されるインスタンスであり、 クラスCtoインスタンスです。残りの引数は、初期化引数リストです。

システムが提供するupdate-instance-for-different-classメソッドは、 2つの特定パラメーターがあり、 どちらもstandard-objectクラスです。 最初、このメソッドは、初期化引数の有効性をチェックし、 もし指定された初期化引数が有効であると宣言されていなかった場合は、 エラーが発せられます(詳細は7.1.2 初期化引数の有効性の宣言を参照)。 それから、このメソッドはジェネリック関数shared-initializeを、 次に示す引数とともに呼び出します。 引数は、新しいインスタンス、新しく追加されるスロット名のリスト、 そして受け取った初期化引数です。

7.2.3 インスタンスのクラスの更新のカスタマイズ

update-instance-for-different-classのメソッドは、 インスタンスを更新するとき、 特定のアクションを定義することができます。 もしupdate-instance-for-different-classafterメソッドのみが定義された場合は、 これはシステムが提供する初期化のメソッドのあとに実行されます。 よってupdate-instance-for-different-classの標準的な動作には干渉しないでしょう。

shared-initializeのメソッドは、クラスの再定義を カスタマイズするために定義されるでしょう。 詳細は7.1.5 Shared-Initializeを参照。

7.3 インスタンスの再初期化

ジェネリック関数reinitialize-instanceは、 初期化引数に従ってスロットの値を変更するときに使用されます。

再初期化のプロセスにより、スロットの値が変更され、 ユーザーが定義するアクションが実行されます。 これはスロットの追加と削除といったインスタンスの構造の修正は行いません。 また、:initformフォームを使ったスロットの初期化を行いません。

ジェネリック関数reinitialize-instanceは、直接呼び出されるでしょう。 これは引数に一つのインスタンスが要求されます。 またreinitialize-instanceshared-initializeによって使用される、 任意の数の初期化引数を受け取ります。 要求されるインスタンスの引数より後の引数は、初期化引数リストの形式でなければなりません。

システムが提供するreinitialize-instanceのメソッドは、 特定パラメーターにstandard-objectクラスを取ります。 最初、メソッドは初期化引数の有効性をチェックし、 もし指定された初期化引数が有効であると宣言されていなかった場合は、 エラーが発せられます(詳細は7.1.2 初期化引数の有効性の宣言を参照)。 このメソッドは、ジェネリック関数shared-initializeを、 次に示す引数とともに呼び出します。 引数は、インスタンスnil、そして受け取った初期化引数です。

7.3.1 再初期化のカスタマイズ

メソッドreinitialize-instanceは、インスタンスを更新するとき、 特定のアクションを定義することができます。 もしreinitialize-instanceafterメソッドのみが定義された場合は、 メソッドはシステムが提供する初期化のメソッドのあとに実行されます。 よってreinitialize-instanceの標準的な動作に干渉しないでしょう。

shared-initializeのメソッドは、クラスの再定義を カスタマイズするために定義されるでしょう。 詳細は7.1.5 Shared-Initializeを参照。

7.4 メタオブジェクト

オブジェクトシステムの実装は、クラス、メソッド、そしてジェネリック関数を扱います。 オブジェクトシステムは、メソッドとクラスによって定義された、 ジェネリック関数の集合を含みます。これらのジェネリック関数の振る舞いは、 オブジェクトシステムの振る舞いを定義します。 これらのメソッドが定義されているクラスのインスタンスは、メタオブジェクトと呼ばれます。

7.4.1 標準メタオブジェクト

オブジェクトシステムは、標準メタオブジェクトと呼ばれる メタオブジェクトの集合を提供します。 これらはstandard-objectクラスと、 standard-method, standard-generic-function, method-combinationの それぞれのクラスのインスタンスを含みます。

  • standard-methodクラスは、defmethoddefgenericフォームによって 定義されるメソッドの標準クラスです。

  • standard-generic-functionクラスは、 defmethod, defgeneric, defclassのフォームによって 定義されるジェネリック関数の標準クラスです。

  • standard-objectという名前のクラスは、standard-classクラスのインスタンスです。 またstandard-objectは、自分自身とstructure-classを除く、 standard-classインスタンスである全てのクラスのスーパークラスです。

すべてのmethod-combinationオブジェクトは、 method-combinationクラスのサブクラスのインスタンスです。

7.5 スロット

7.5.1 スロットの紹介

standard-classメタクラスのオブジェクトは、 0個かそれ以上の名前の付いたスロットを持ちます。 オブジェクトのスロットは、オブジェクトのクラスによって決められます。 各スロットは、値を保有できます。 スロットの名前は、変数名として使うのに有効な構文のシンボルです。

スロットが値を持っていないときは、そのスロットはunboundであると言われます。 もしunboundのスロットを読み込んだ場合は、 ジェネリック関数のslot-unboundが呼び出されます。 システムが提供するslot-unboundのメソッドでは、 引数の特定パラメーターがtクラスのものが提供されており、エラーが発せられます。 もしslot-unboundが値を返却する場合は、 第一返却値はスロットの値として、そのときに使用されるものとなります。

スロットのデフォルト値フォームは、 スロットオプション:initformによって定義されます。 :initformフォームに値が提供された場合は、 defclassが評価された中のレキシカル環境にてフォームが評価されます。 defclassが評価された中のレキシカル環境に沿った:initiformのことを、 補足された初期化フォームと呼びます。 詳細は7.1 オブジェクトの作成と初期化を参照。

局所スロットとして定義されたスロットは、 正確に一つのインスタンスがアクセス可能です。 すなわち、唯一つのスロットが確保されます。 共有スロットとして定義されたスロットは、 クラスとそのサブクラスによって与えられる、複数のインスタンスから見ることができます。

defclassフォームによるクラスが、スロット特定子に名前を含んでいたとき、 クラスは名前が与えられたスロットが定義されたと言います。 局所スロットの宣言では、即座にはスロットが作成されません。 なぜならクラスのインスタンスが作成されるときに、スロットが作成されるからです。 共有スロットは宣言では、即座にスロットを作成します。

defclassのスロットオプション:allocationは、スロットの定義時に種類を指定します。 もしスロットオプション:allocationの値が:instanceならば、局所スロットが作成されます。 もしスロットオプション:allocation:classならば、共有スロットが作成されます。

もしスロットがインスタンスのクラスによって定義された場合、 あるいはクラスのスーパークラスから継承された場合は、 スロットはクラスのインスタンスからアクセス可能であると言います。 インスタンスからは、せいぜい一つの名前付きスロットがアクセス可能です。 クラスによって定義された共有スロットは、 クラスのすべてのインスタンスからアクセス可能です。 スロットの継承による詳細な説明は、7.5.3 スロットの継承とスロットオプションを参照。

7.5.2 スロットへのアクセス

スロットは次の2つの方法にてアクセスできます。 関数slot-valueを使用する方法、 そしてdefclassフォームによって生成されるジェネリック関数を使用する方法です。

関数slot-valueは、 defclassフォームで対象のクラスのインスタンスにて アクセス可能に設定したスロットに対しては、 どんなスロットの名前でも指定してアクセスすることができます。

マクロdefclassは、スロットの読み書きをするメソッドを生成するための構文が提供されています。 もしreaderのメソッドが要求された場合、 スロットの値を読むためのメソッドが自動的に生成されますが、 ただし値を格納するためのメソッドは生成されません。 もしwriterのメソッドが要求された場合、 スロットの値を書き込むためのメソッドが自動的に生成されますが、 ただし値を読み込むためのメソッドは生成されません。 もしaccessorのメソッドが要求された場合は、 スロットの値を読むためのメソッドと、 スロットの値を書き込むためのメソッドが自動的に生成されます。 readerwriterのメソッドは、slot-valueを使用して実装されます。

スロットに対してreaderwriterのメソッドを指定するときは、 ジェネリック関数の名前とそれに沿って生成されるメソッドの名前を直接指定します。 もしwriterメソッドの名前をシンボルnameに指定した場合、 スロットへ書き込むためのジェネリック関数の名前がシンボルnameとなり、 そのジェネリック関数の引数は、 新しい値、インスタンスの順に2つ取ります。 もしaccessorメソッドの名前をシンボルnameに指定した場合、 スロットから読み込むためのジェネリック関数の名前がシンボルnameとなり、 そしてスロットへ書き込むためのジェネリック関数の名前が、リストの(setf name)となります。

スロットオプションの:reader, :writer, :accessorの指定により、 作成か修正が行われたジェネリック関数は、 正確に普通のジェネリック関数として扱うことができます。

注意として、slot-valueはスロットから値を読み込むか書き込む時に使われますが、 そのスロットのreaderwriterのメソッドが存在するかどうかに関わらず使用できます。 slot-valueが使われる時、readerwriterのメソッドは実行されません。

マクロwith-slotsは、 指定されたスロットがレキシカル変数として使えるようにするレキシカル環境を確立します。 マクロwith-slotsは、指定したスロットへアクセスするために関数slot-valueを実行します。

マクロwith-accessorsは、 指定したスロットがレキシカル変数としてスロットのaccessorを通して 使えるようにするレキシカル環境を確立します。 マクロwith-accessorsは、指定したスロットへアクセスするために、 適切なaccessorを実行します。

7.5.3 スロットの継承とスロットオプション

クラスCインスタンスについて、アクセス可能な全てのスロットの名前の集合は、 クラスCとそのスーパークラスによって定義されたスロットの名前の集合の和集合となります。 インスタンスの構造は、そのインスタンスの局所スロットの名前の集合です。

単純な場合として、たった1つのクラスCと、そのスーパークラスにて、 名前ありのスロットを定義したとします。 もしスロットがCスーパークラスによって定義された場合、 そのスロットは継承されたと言えます。 スロットの特性は、クラス定義のスロット指定子によって決定されます。 スロットSを定義したクラスを考えます。 もしスロットオプション:alocation:instanceならば、Sは局所スロットであり、 Cの各インスタンスSと名前の付いた独自のスロットをもち、 Sには独自の値が格納されます。 もしスロットオプション:allocation:classならば、 Sは共有スロットであり、Sが定義されたクラスに値が格納されます。 そしてCの全てのインスタンスは、その1つのスロットにアクセスできます。 もしスロットオプション:allocationが省略された場合は、:instanceが使用されます。

一般的に、複数のクラスである、Cとそのスーパークラスは、 1つの名前付きのスロットを定義できます。 そのような場合、Cインスタンスに対しては、 与えられた名前に対してただ1つのスロットがアクセス可能です。 そしてそのスロットの特性は、いくつかのスロットの指定子を 計算によって結び付けたものになります。 計算方法を次に示します。

  • 1つのスロットの名前に対する全てのスロット指定子は、 クラスCのクラス優先順位リストのクラスに従って、 最も特定的なものからそうでないものへ順序付けられます。 どのスロット指定子の直下に特定されるかの全ての参照は、この順序に従って調査されます。

  • スロットの確保は、最も特定的なスロット指定子によって制御されます。 もし最も特定的なスロット指定子がスロットオプション:allocationを含んでいなかった場合は、 :instanceが使用されます。 特定的ではないスロット指定子は、確保には影響しません。

  • スロットのデフォルト初期値フォームは、 スロットオプション:initformが含まれる最も特定的なスロット指定子の、 :initformの値になります。 もしスロット指定子が:initformを持っていなかった場合、 スロットはデフォルト初期値フォームを持ちません。

  • スロットの値は、常に型(and T1 ... Tn)となるでしょう。 ここでT1 ... Tnとは、全てのスロット指定子が持っているスロットオプション:typeの値です。 もしスロット指定子がスロットオプション:typeを持っていなかった場合は、 スロットの値は常に型tとなります。 スロットの型に合っていない値をスロットに格納しようとした結果については未定義です。

  • 指定したスロットを初期化する際に使用する初期化引数の集合は、 全てのスロット指定子の中のスロットオプション:initargで定義された初期化引数の和集合です。

  • スロットのドキュメント文字列は、 スロットオプション:documentationが含まれる最も特定的なスロット指定子の、 :documentationの値になります。 もしスロット指定子が:documentationを持っていなかった場合、 スロットはドキュメント文字列を持ちません。

スロットの確保の規則では、共有スロットはシャドウすることができます。 例えば、もしクラスC1が、スロットの名前S、 スロットオプション:allocation:classのスロットを定義した場合、 そのスロットは、C1とその全てのサブクラスのインスタンスからアクセス可能です。 しかし、もしC2C1のサブクラスであり、C2が名前Sのスロットを定義した場合、 C2とその全てのサブクラスのインスタンスでは、C1のスロットは共有されません。 クラスC1が共有スロットを定義したときは、 次の条件の時にC1のどんなサブクラスC2でもその単一のスロットは共有されます。 それは、C2defclassフォームで同じ名前のスロットを定義していないとき。 あるいは、C2のクラス優先リスト内において 同じ名前のスロットを定義しているクラスを見たとき、 C1よりも先導しているものがC2スーパークラスに存在していない場合です。

型の規則による結果は、スロットの値が関連するスロットの 各スロット指定子の型の条件を満たすことです。 スロットの型の条件が守られていない値を スロットに格納しようとした際の結果は未定義なので、 スロットの値は型の条件の安全性を失うでしょう。

スロットオプション:reader, :writer, :accessorは、 スロットの特性を宣言すると言うよりは、 メソッドを作成するものです。 readerwriterメソッドは、 7.6.7 メソッドの継承で説明される定義により継承されます。

スロットにアクセスするメソッドは、スロットの名前と、 スロットの値の型のみを使用します。 例えば、スーパークラスが、 指定した名前により共有スロットにアクセスすることを期待するメソッドを提供した場合、 またサブクラスが同じ名前で局所スロットを定義した場合を考えます。 もしスーパークラスによって提供されたメソッドを、 サブクラスのインスタンス上で使用した場合、 メソッドは局所スロットにアクセスします。

7.6 ジェネリック関数とメソッド

7.6章は7章Objectsのジェネリック関数関連の和訳を参照。