For Development HEAD DRAFTSearch (procedure/syntax/module):

7.3 インスタンス

この節では、インスタンスの生成のしかたと使い方について説明します。


7.3.1 インスタンスの作成

クラスオブジェクトをつかうと、ジェネリック関数 make でその クラスのインスタンスを生成できます。標準の <class> に対して 特定化されたメソッドは以下のとおり定義されています。

Generic Function: make
Method: make (class <class>) arg …

class のインスタンスを生成し、それを返します。arg … は 典型的な場合には、そのインスタンスを初期化するためのキーワード値のリストです。

概念としては、デフォルトの make メソッドは以下のように定義されて います。

(define-method make ((class <class>) . initargs)
  (let ((obj (allocate-instance class initargs)))
    (initialize obj initargs)
    obj))

すなわち、最初、class のインスタンス用にメモリをアロケートし、 それから、initialize メソッドを用いてそれを初期化します。

Generic Function: allocate-instance
Method: allocate-instance (class <class>) initargs

classの新規にアロケートされた、初期化されていないインスタンスを返します。

Generic Function: initialize
Method: initialize (obj <object>) initargs

<object> に対するデフォルトの初期化メソッドは以下のように働きます。

  • そのクラスの初期化可能なスロットのそれぞれに対して、
    • もし (そのスロットが :init-keyword スロットオプションを持ち、「かつ」 そのキーワードが initargs 中にある) そのときは、対応する値がそのスロットを初期化するのに使われます。
    • そうではないとき、もし、そのスロットが、:init-value スロットオプションを 持てば、そのオプションに与えられた値がそのスロットを初期化するのに使われます。
    • そうでもないとき、もし、そのスロットが、:init-thunk スロットオプションを 持てば、その thunk が呼ばれその返り値が、そのスロットを初期化するのに 使われます。
    • さらにどれでもなければ、そのスロットは未束縛のままです。

デフォルトのスロットアロケーションクラスのなかで、インスタンスアロケート スロットだけが初期化可能で、上の流れで処理されます。クラスアロケート スロット(すなわち、スロットアロケーションが :class あるいは :each-subclass のどちらかの場合)は、:init-value あるいは :init-form スロットオプションが与えられていれば、クラスオブジェクト 生成時に初期化されます。仮想スロットが、初期化されることはありません。

ユーザ定義アロケーションクラスは、初期化可能にも不可能にも設定することが できます。詳しくは メタオブジェクトプロトコル を参照してください。

initialize メソッドを特定化する場合には next-method が 確実に呼ばれるようにして、新しく生成されたインスタンスのすべての スロットにアクセスする前にデフォルトの流れで、そのスロットが正しく 初期化されるようにしてください。

定義したクラスに対応する initialize メソッドを特定化して インスタンスの初期化の方法をカスタマイズするというのが、典型的な やりかたです。

allocate-instance メソッドを特定化するというのは一般的な方法では ありません。しかしながら、make がどのように働くかを知っているなら、 make そのものを特定化して、なんらかの状況(たとえば、あらかじめ アロケートしてあるインスタンスを使うというような状況)でインスタンスの アロケーションを回避できます。


7.3.2 インスタンスへのアクセス

標準アクセサ

Function: slot-ref obj slot

オブジェクトobjのスロットslotの値を返します。

指定したスロットが値に束縛されていない場合、ジェネリック関数 slot-unboundが3つの引数、objのクラス、objslot を伴って呼び出されます。slot-unboundのデフォルトの振る舞いは、 エラーの通知です。

オブジェクトが指定されたスロットを持っていない場合は、ジェネリック関数 slot-missingが3つの引数、objのクラス、objslotを 伴って呼び出されます。slot-missingのデフォルトの振る舞いは、 エラーの通知です。

Function: slot-set! obj slot value

オブジェクトobjのスロットslotの値を、valueに セットします。

オブジェクトが指定したスロットを持っていない場合は、ジェネリック関数 slot-missingが4つの引数、objのクラス、objslotvalueを伴って呼び出されます。

Function: slot-bound? obj slot

オブジェクトobjのスロットslotが束縛されていれば真を、 そうでなければ偽を返します。

オブジェクトが指定したスロットを持っていない場合は、ジェネリック関数 slot-missingが3つの引数、objのクラス、objslotを伴って呼び出されます。

Function: slot-exists? obj slot

objslotを持っていれば真を返します。

Function: slot-push! obj slot value

この関数は、一般的なイディオムの実装です。 これは以下のようなコードで定義できます(が、将来のバージョンでは 最適化されるでしょう)。

(define (slot-push! obj slot value)
  (slot-set! obj slot (cons value (slot-ref obj slot))))
Function: slot-pop! obj slot :optional fallback

slot-push!と逆の操作です。objslotの値が ペアの場合、そのcarをslotの値から取り除き、取り除かれた値を返します。

slotの値がペアでない、あるいはslotが未束縛の場合、 fallbackが与えられていればそれが返され、そうでなければエラーが報告されます。

Method: ref (obj <object>) (slot <symbol>)
Method: (setter ref) (obj <object>) (slot <symbol>) value

これらのメソッドはそれぞれ、単に slot-ref および slot-set! を呼ぶだけです。直接 slot-refslot-set! を呼ぶよりも 効率はすこし悪いですが、プログラムコードはコンパクトになります。

フォールバックメソッド

Generic Function: slot-unbound
Method: slot-unbound (class <class>) obj slot

このジェネリック関数は束縛されていないスロットの値を取り出そうとしたときに 呼び出されます。このジェネリック関数の返り値は値を得ようとした呼出しもとに 返されます。

デフォルトのメソッドは単にエラーのシグナルをあげるだけです。

Generic Function: slot-missing
Method: slot-missing (class <class>) obj slot :optional value

このジェネリック関数は存在しないスロットの値を取り出そうとしたとき、あるいは 設定しようとしたときに呼びだされます。このジェネリック関数の返り値は、 値を得ようとした呼出しもとに返されます。

デフォルトのメソッドは単にエラーのシグナルをあげるだけです。

特殊アクセサ

Function: current-class-of obj

obj のクラスメタオブジェクトを返します。obj のクラスが 再定義されてしまった場合でも、obj がその変更に合せて更新されて いない場合には、この手続きは obj の元のクラスを返します。 この手続きは、obj を更新しません。

この手続きはめったに必要にはなりません。必要になるのは change-class メソッド内で、obj の更新のトリガーを引きたくないような場合 (無限ループを起す可能性がある場合)です。

Function: class-slot-ref class slot-name
Function: class-slot-set! class slot-name obj
Function: class-slot-bound? class slot-name obj

スロットの:allocationオプションが:classもしくは :each-subclassである場合、これらの手続きを使って、 インスタンス無しでそれらのスロットの値を取得/設定できます。

Method: slot-ref-using-class (class <class>) (obj <object>) slot-name
Method: slot-set-using-class! (class <class>) (obj <object>) slot-name value
Method: slot-bound-using-class? (class <class>) (obj <object>) slot-name

slot-refslot-set!slot-bound? のジェネリック 関数版です。classobject のクラスでなければなりません。

これらの関数は、ジェネリックであることに加えて、 obj のクラスが再定義されてもクラスの再定義を起動しない (そのような場合、classobj の元々のクラスで なければならない)という点で手続き版とは違います。

覚書: CLOS とはちがい、slot-ref などは、その中でジェネリック 関数版を呼ぶことはありません。それゆえ、slot-ref-using-class を 特定化することによって、slot-ref をカスタマイズすることはできません。 つまり、これらのジェネリック関数の主たる目的は change-class メソッドの内部で使われることです。とくに、slot-ref などは クラス再定義を再度起動する(詳細については クラスの変更 を 参照)ので、obj の再定義中には使えません。


7.3.3 インスタンスの記述

REPLで,dもしくは,describeトップレベルコマンドを使うと オブジェクトの詳しい情報が表示されます(REPLでの開発参照)。 これは、ジェネリックファンクションdescribeにより実現されています。

Generic Function: describe obj

Schemeオブジェクトobjの詳細情報を表示します。 既定メソッドはobjのクラスを表示し、さらにobjがスロットを 持っていればその名前と内容をリストします。

gosh> (describe (sys-stat "Makefile"))
#<<sys-stat> 0x1e7de60> is an instance of class <sys-stat>
slots:
  type      : regular
  perm      : 436
  mode      : 33204
  ino       : 3242280
  dev       : 2097
  rdev      : 0
  nlink     : 1
  uid       : 500
  gid       : 500
  size      : 19894
  atime     : 1435379061
  mtime     : 1432954340
  ctime     : 1432954340

gauche.interactiveモジュールをロードした場合 (REPLを使っていれば自動的にロードされています)、多くの組み込みオブジェクトに対して describeメソッドが追加されるので、それぞれのクラスに応じた 詳細情報が表示されます。詳しくはgauche.interactive - インタラクティブセッションを見てください。

自分で定義したクラスに特殊化したdescribeメソッドを定義することで、 記述をカスタマイズすることができます。 describeメソッドに共通するパターンは: (1) まずdescribe-commonを呼びます。これは共通した最初の一行を表示します。 (2) 最後に(values)を呼んで値を返さないようにします。 これはREPLで便利です。 その間では好きな情報を表示できます。

(define-method describe ((obj <my-class>))
  ;; common opening line, "#<...> is an instance of class <my-class>"
  (describe-common obj)
  ;; print relevant information
  ... custom description ...
  ;; if you have slots to show:
  (describe-slots obj)
  ;; return 0 values.  This works nicely in REPL.
  (values))
Function: describe-common obj

この手続きはdescribeで共通して表示される最初の行を出力します。 カスタムdescribeメソッドはこれを最初に呼んでください。

(describe-common 1)
 ⇒ prints (a b c) is an instance of class <pair>
Function: describe-slots obj

この手続きはデフォルトのdescribeメソッドがやるように、 objのスロットとその値のリストを表示します。 カスタムのdescribeメソッドの中で、他の情報と一緒にスロットの情報を 表示したい場合に使えます。

(describe-slots (current-input-port))
 ⇒ prints
slots:
  name      : "(standard input)"
  buffering : :full
  sigpipe-sensitive?: #f
  current-line: 7
  current-column: 0
  link      : #<oport (standard output) 0x7ff0cc6cddd0>

デフォルトでは、この手続きは%で始まる名前を持つスロットを表示しません。 これは内部的に使われるスロットを%で始める慣習を反映したものです。 オブジェクトシステムとしては、特定の名前のスロットを「隠す」機能はありません。 これは純粋に、describeの出力を余分な情報で溢れさせないための ユーザインタフェースとしての機能です。 必要なら、動的状態describe-detailsを真の値に設定することで、 describe-slotsが全てのスロットを表示するようにできます。

Function: describe-details :optional flag

これはグローバルな動的状態で、describeが補助的な詳細を表示するかどうかを制御します。 引数なしで呼ばれた場合が現在の状態を返します。引数flagを与えた場合、 それは真偽値でなければならず、flagの値が現在の状態となり、 直前の状態が返されます。

デフォルトの値は#fで、describeは詳細を表示しません。 特に、オブジェクトのスロット名が%ではじまるものは省略されます。

今のところ、状態はグローバルであり、変更はスレッドセーフではありません。 この状態はプログラムで頻繁にいじるものではなく、 REPLユーザインタフェースの調整パラメタと考えているためです。 ただ、将来、便利だとわかればこれをスレッドセーフにしたり、 パラメータにしたりするかもしれません。


7.3.4 クラスの変更

クラス変更プロトコル

CLOS系のオブジェクトシステムのユニークな機能は既存のインスタンスの クラスを変更できるということです。新旧二つのクラスに関連性がある必要は ありません。おのぞみなら、ミシンを雨がさに変更することもできます。

Generic Function: change-class
Method: change-class (obj <object>) (new-class <class>)

オブジェクト obj のクラスを new-class に変更します。 デフォルトのメソッドは単に change-object-class 手続きを呼ぶだけです。

Function: change-object-class obj orig-class new-class

オブジェクト obj のクラスを orig-class から new-class に変更します。これはジェネリック関数ではありません。 オブジェクトのクラスを変更するにはちょっとした秘密の内部的操作が必要で、 この手続きはそれを隠蔽しています。

クラスを変更する正確なステップは以下のようになっています。

  1. new-class の新しいインスタンスが allocate-instance によって アロケートされる。
  2. new-classの各スロットに対して、
    1. もし、そのスロットが old-class に存在し、obj で束縛されていれば、 その値は obj から取り出され、新しいインスタンスにセットされる。 (そのスロットは持ち越されます。)
    2. そうでなければ、新しいインスタンスのスロットは、インスタンスの作成 で 説明された手順で、標準のスロット初期化プロトコルによって初期化されます。
  3. 最後に、新しいインスタンスの内容が obj移植されます。すなわち、 obj がアイデンティティを変えることなく new-class のインスタンス となります。

objに対してnew-classinitialize メソッドは呼ばれないことに 注意してください。必要なら、独自の change-class メソッドを 定義してinitializeを呼ぶようにすることができます。

change-object-classobj を返します。

ユーザは大抵の場合、change-object-classを直接呼ぶ必要は無いでしょう。 そのかわり、特定化した change-class を定義するべきです。 たとえば、旧いクラスのスロット x を 新しいクラスのスロット y へ持ち越すことは、 こんな風に書けば可能です。

(define-method change-class ((obj <old-class>) <new-class>)
  (let ((old-val (slot-ref obj 'x)))
    (next-method)               ;; calls default change-class
    (slot-set! obj 'y old-val)  ;; here, obj's class is already <new-class>.
    obj))

インスタンス更新のカスタマイズ

再定義されたクラスのインスタンスが更新される場合は、それもクラス変更として 扱われます。オブジェクトは通常のスロットアクセサ/モディファイア 経由でアクセスされるときに、そのクラスが再定義されたかどうかを チェックされます。もし再定義が行われていれば、 再定義されたクラスをnew-classとして change-classが呼ばれます。 すなわち、インスタンスの更新はオブジェクトの クラスを元々のものから再定義されたものへ変更することと看倣されます。

change-class を特定化することで、インスタンスを再定義された クラス用に更新する方法をカスタマイズできます。しかし、クラス再定義 用の change-class を書くには特別な注意が必要です。

まず、再定義はクラスオブジェクトのグローバルな束縛を変更してしまいます。 それゆえ、クラス再定義がおこる前の旧いクラスへの参照を保持しておく 必要があり、change-class メソッドの特定化をするには、この旧い クラスを使う必要があります。

;; save old <myclass>
(define <old-myclass> <myclass>)

;; redefine <myclass>
(define-class <myclass> ()
  ...)

;; define customized change-class method
(define-method change-class ((obj <old-myclass>) <myclass>)
  ...
  (next-method)
  ...)

次に、上の change-class メソッドは、slot-refslot-set!class-of などを経由して、暗黙のうちに起動される得ることに 注意してください。もし、change-class 内で、obj に対して 再度 slot-ref のような手続き使うと、インスタンス更新プロトコルが 再帰的に起動され、無限ループをひきおこすことになります。インスタンス 更新を起動しないようなメソッドしか使えません。すなわち、 slot-ref-using-classslot-set-using-class!slot-bound-using-class?current-class-of しか使えません。

仮想スロットのように、手続きによって計算される値をもつスロットを 持ち越したいのであれば、slot-ref その他が、そのスロットの値を 計算している最中に、暗黙裏に obj に対して呼ばれることがありえます。 実際のところは、change-object-class はこのような再帰を検出する 保護機構をもっています。もし、このようなことが起これば、 change-object-class はそのスロットの値を取り出すのを諦め、 新しいインスタンスのスロットを旧いスロットが未束縛であるとして、 初期化します。

インスタンス更新をカスタマイズするのは非常に強力ですがたいへん トリッキーな仕事です。Gauche のソース中のテストプログラム には自明ではないいくつかのケースが含まれています。test/object.scm 見てみてください。



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT