この節では、インスタンスの生成のしかたと使い方について説明します。
• インスタンスの作成: | ||
• インスタンスへのアクセス: | ||
• インスタンスの記述: | ||
• クラスの変更: |
クラスオブジェクトをつかうと、ジェネリック関数 make
でその
クラスのインスタンスを生成できます。標準の <class>
に対して
特定化されたメソッドは以下のとおり定義されています。
class のインスタンスを生成し、それを返します。arg … は 典型的な場合には、そのインスタンスを初期化するためのキーワード値のリストです。
概念としては、デフォルトの make
メソッドは以下のように定義されて
います。
(define-method make ((class <class>) . initargs) (let ((obj (allocate-instance class initargs))) (initialize obj initargs) obj))
すなわち、最初、class
のインスタンス用にメモリをアロケートし、
それから、initialize
メソッドを用いてそれを初期化します。
classの新規にアロケートされた、初期化されていないインスタンスを返します。
<object>
に対するデフォルトの初期化メソッドは以下のように働きます。
デフォルトのスロットアロケーションクラスのなかで、インスタンスアロケート
スロットだけが初期化可能で、上の流れで処理されます。クラスアロケート
スロット(すなわち、スロットアロケーションが :class
あるいは
:each-subclass
のどちらかの場合)は、:init-value
あるいは
:init-form
スロットオプションが与えられていれば、クラスオブジェクト
生成時に初期化されます。仮想スロットが、初期化されることはありません。
ユーザ定義アロケーションクラスは、初期化可能にも不可能にも設定することが できます。詳しくは メタオブジェクトプロトコル を参照してください。
initialize
メソッドを特定化する場合には next-method
が
確実に呼ばれるようにして、新しく生成されたインスタンスのすべての
スロットにアクセスする前にデフォルトの流れで、そのスロットが正しく
初期化されるようにしてください。
定義したクラスに対応する initialize
メソッドを特定化して
インスタンスの初期化の方法をカスタマイズするというのが、典型的な
やりかたです。
allocate-instance
メソッドを特定化するというのは一般的な方法では
ありません。しかしながら、make
がどのように働くかを知っているなら、
make
そのものを特定化して、なんらかの状況(たとえば、あらかじめ
アロケートしてあるインスタンスを使うというような状況)でインスタンスの
アロケーションを回避できます。
オブジェクトobjのスロットslotの値を返します。
指定したスロットが値に束縛されていない場合、ジェネリック関数
slot-unbound
が3つの引数、objのクラス、obj、slot
を伴って呼び出されます。slot-unbound
のデフォルトの振る舞いは、
エラーの通知です。
オブジェクトが指定されたスロットを持っていない場合は、ジェネリック関数
slot-missing
が3つの引数、objのクラス、obj、slotを
伴って呼び出されます。slot-missing
のデフォルトの振る舞いは、
エラーの通知です。
オブジェクトobjのスロットslotの値を、valueに セットします。
オブジェクトが指定したスロットを持っていない場合は、ジェネリック関数
slot-missing
が4つの引数、objのクラス、obj、
slot、valueを伴って呼び出されます。
オブジェクトobjのスロットslotが束縛されていれば真を、 そうでなければ偽を返します。
オブジェクトが指定したスロットを持っていない場合は、ジェネリック関数
slot-missing
が3つの引数、objのクラス、obj、
slotを伴って呼び出されます。
objがslotを持っていれば真を返します。
この関数は、一般的なイディオムの実装です。 これは以下のようなコードで定義できます(が、将来のバージョンでは 最適化されるでしょう)。
(define (slot-push! obj slot value) (slot-set! obj slot (cons value (slot-ref obj slot))))
slot-push!
と逆の操作です。objのslotの値が
ペアの場合、そのcarをslotの値から取り除き、取り除かれた値を返します。
slotの値がペアでない、あるいはslotが未束縛の場合、 fallbackが与えられていればそれが返され、そうでなければエラーが報告されます。
これらのメソッドはそれぞれ、単に slot-ref
および slot-set!
を呼ぶだけです。直接 slot-ref
や slot-set!
を呼ぶよりも
効率はすこし悪いですが、プログラムコードはコンパクトになります。
このジェネリック関数は束縛されていないスロットの値を取り出そうとしたときに 呼び出されます。このジェネリック関数の返り値は値を得ようとした呼出しもとに 返されます。
デフォルトのメソッドは単にエラーのシグナルをあげるだけです。
このジェネリック関数は存在しないスロットの値を取り出そうとしたとき、あるいは 設定しようとしたときに呼びだされます。このジェネリック関数の返り値は、 値を得ようとした呼出しもとに返されます。
デフォルトのメソッドは単にエラーのシグナルをあげるだけです。
obj のクラスメタオブジェクトを返します。obj のクラスが 再定義されてしまった場合でも、obj がその変更に合せて更新されて いない場合には、この手続きは obj の元のクラスを返します。 この手続きは、obj を更新しません。
この手続きはめったに必要にはなりません。必要になるのは change-class
メソッド内で、obj の更新のトリガーを引きたくないような場合
(無限ループを起す可能性がある場合)です。
スロットの:allocation
オプションが:class
もしくは
:each-subclass
である場合、これらの手続きを使って、
インスタンス無しでそれらのスロットの値を取得/設定できます。
slot-ref
、slot-set!
、slot-bound?
のジェネリック
関数版です。class は object のクラスでなければなりません。
これらの関数は、ジェネリックであることに加えて、 obj のクラスが再定義されてもクラスの再定義を起動しない (そのような場合、classは obj の元々のクラスで なければならない)という点で手続き版とは違います。
覚書: CLOS とはちがい、slot-ref
などは、その中でジェネリック
関数版を呼ぶことはありません。それゆえ、slot-ref-using-class
を
特定化することによって、slot-ref
をカスタマイズすることはできません。
つまり、これらのジェネリック関数の主たる目的は change-class
メソッドの内部で使われることです。とくに、slot-ref
などは
クラス再定義を再度起動する(詳細については クラスの変更 を
参照)ので、obj の再定義中には使えません。
REPLで,d
もしくは,describe
トップレベルコマンドを使うと
オブジェクトの詳しい情報が表示されます(REPLでの開発参照)。
これは、ジェネリックファンクションdescribe
により実現されています。
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))
この手続きはdescribe
で共通して表示される最初の行を出力します。
カスタムdescribe
メソッドはこれを最初に呼んでください。
(describe-common 1)
⇒ prints (a b c) is an instance of class <pair>
この手続きはデフォルトの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
が全てのスロットを表示するようにできます。
これはグローバルな動的状態で、describe
が補助的な詳細を表示するかどうかを制御します。
引数なしで呼ばれた場合が現在の状態を返します。引数flagを与えた場合、
それは真偽値でなければならず、flagの値が現在の状態となり、
直前の状態が返されます。
デフォルトの値は#f
で、describe
は詳細を表示しません。
特に、オブジェクトのスロット名が%
ではじまるものは省略されます。
今のところ、状態はグローバルであり、変更はスレッドセーフではありません。 この状態はプログラムで頻繁にいじるものではなく、 REPLユーザインタフェースの調整パラメタと考えているためです。 ただ、将来、便利だとわかればこれをスレッドセーフにしたり、 パラメータにしたりするかもしれません。
CLOS系のオブジェクトシステムのユニークな機能は既存のインスタンスの クラスを変更できるということです。新旧二つのクラスに関連性がある必要は ありません。おのぞみなら、ミシンを雨がさに変更することもできます。
オブジェクト obj のクラスを new-class に変更します。
デフォルトのメソッドは単に change-object-class
手続きを呼ぶだけです。
オブジェクト obj のクラスを orig-class から new-class に変更します。これはジェネリック関数ではありません。 オブジェクトのクラスを変更するにはちょっとした秘密の内部的操作が必要で、 この手続きはそれを隠蔽しています。
クラスを変更する正確なステップは以下のようになっています。
allocate-instance
によって
アロケートされる。
objに対してnew-classのinitialize
メソッドは呼ばれないことに
注意してください。必要なら、独自の change-class
メソッドを
定義してinitialize
を呼ぶようにすることができます。
change-object-class
は obj を返します。
ユーザは大抵の場合、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-ref
、slot-set!
、
class-of
などを経由して、暗黙のうちに起動される得ることに
注意してください。もし、change-class
内で、obj に対して
再度 slot-ref
のような手続き使うと、インスタンス更新プロトコルが
再帰的に起動され、無限ループをひきおこすことになります。インスタンス
更新を起動しないようなメソッドしか使えません。すなわち、
slot-ref-using-class
、slot-set-using-class!
、
slot-bound-using-class?
、current-class-of
しか使えません。
仮想スロットのように、手続きによって計算される値をもつスロットを
持ち越したいのであれば、slot-ref
その他が、そのスロットの値を
計算している最中に、暗黙裏に obj に対して呼ばれることがありえます。
実際のところは、change-object-class
はこのような再帰を検出する
保護機構をもっています。もし、このようなことが起これば、
change-object-class
はそのスロットの値を取り出すのを諦め、
新しいインスタンスのスロットを旧いスロットが未束縛であるとして、
初期化します。
インスタンス更新をカスタマイズするのは非常に強力ですがたいへん
トリッキーな仕事です。Gauche のソース中のテストプログラム
には自明ではないいくつかのケースが含まれています。test/object.scm
見てみてください。