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

9.28 gauche.record - レコード型

Module: gauche.record

このモジュールは、レコード型、すなわち、ユーザ定義の集合型を定義する機能を提供します。 APIは、SRFI-9 (Defining Record Types) および SRFI-99 (ERR5RS Records) の上位互換です。

レコード型は、Gaucheのクラスとして実装されていますが、 一般的なクラスとは異なる特徴を持っています。 どういった時にレコード型を使うと良いかについては イントロダクションを参照してください。

レコードAPIは、SRFI-99およびR6RSの設計に従い、3つの層から構成されています。

構文層は、define-record-typeマクロで、レコード型と関連する手続き (コンストラクタ、述語、アクセサ、モディファイア)を、 一括して宣言的に定義します。このマクロを知っているだけで、 ほとんどの一般的なレコードの使用方法においては十分です。

インスペクション層は、レコードおよびレコード型の情報を問い合わせるための 共通の手続きを定義します。

手続き層は、構文層を実装するための低レベルの機構です。 日常的なプログラミングにおいて使う必用はありませんが、 実行時にその場でレコード型を作成するような場合には便利かもしれません。


9.28.1 イントロダクション

Gaucheは、オブジェクトシステム(オブジェクトシステム参照)により、 新しい型を新しいクラスとして定義する一般的な方法を提供しています。 そして、実際にレコード型はGaucheのクラスとして実装されています。 しかし、クラスではなくレコード型を使うことには、いくつかの利点があります。

レコード型の欠点は、Gaucheのクラス再定義プロトコル(クラスの再定義を参照) を利用できないことです。 すなわち、同じ名前でレコードを再定義すると、 古いものとは無関係の新しいレコード型が作成されます。 古い定義で作成されたレコードのインスタンスは、 新しい定義によって更新されることはありません。

さらに重要なこととして、 レコードのコンストラクタ、アクセサ、および、モディファイアは、 パフォーマンス向上のため、使用箇所でインライン展開される傾向があります。 インライン展開により、これらの手続きを使用するコードは、 レコード型が再定義されても影響を受けません。 これは、レコード型を再定義した場合、レコードのコンストラクタ、アクセサ、 または、モディファイアを使用するソースは、 再ロード(再コンパイル)する必用があることを意味します。


9.28.2 構文層

Macro: define-record-type type-spec ctor-spec pred-spec field-spec …

[R7RS base][SRFI-9][SRFI-99+] {gauche.record} レコード型を定義し、オプションとしてコンストラクタ、述語、 フィールドのアクセサ、および、モディファイアを定義します。

type-spec引数は、レコード型に名前を付けます。 また、オプションとしてスーパータイプ(parent)を指定します。

type-spec : type-name
                | (type-name parent option ...)

type-name : identifier
parent : expression
option ... : keyword-value list

type-name識別子は、 レコード型記述子(record type descriptor)すなわちrtdに束縛され、 イントロスペクションやリフレクションのために使用されます。 レコード型に対して可能な操作については、インスペクション層手続き層を参照してください。 Gaucheでは、レコード型記述子は、メタクラス<record-meta>を持つ <class>です。

parent式は、レコード型記述子に評価されなければなりません。 これが与えられると、定義されるレコード型はそれを継承します。 すなわち、親の型で定義されたすべてのスロットは、 type-nameでも利用可能です。そして、type-nameのインスタンスは、 親の型の述語に対しても#tを返します。

レコード型もまたクラスであるため、親の型は定義されるレコード型の スーパークラスでもあります。 しかし、レコード型には単一継承のみという制限があります。 そして、親の型は<record>のサブクラスである必要があります。 (ただし、メインの継承に加えて抽象クラスを持つことができます。 下記のmixinsを参照してください。)

疑似レコード型を定義するために、疑似レコード型のベースタイプを parentに指定することができます。これによって、 ベクタのような通常の集合型を、レコードとしてアクセスできるようになります。 詳しくは、疑似レコード型を参照してください。

option … の部分は、Gaucheの拡張になります。それは、 キーワードと値のリストである必要があります。次のキーワードが認識されます。

:mixins (class …)

補助的なスーパークラスを指定します。そのクラスは抽象クラス、すなわち、 スロットを持たないクラスである必要があります。 これは、レコード型でプロトコルを実装するためのものです (例えば、<sequence> (gauche.sequence - シーケンスフレームワークを参照) 等)。

:metaclass metaclass

メタクラスを変更する場合に指定します。 デフォルトでは、レコード型のメタクラスは<record-meta>です。 他のメタクラスを指定する場合、それは<record-meta>のサブクラスである 必用があります。

ctor-specは、レコードのインスタンスを作るコンストラクタを定義します。

ctor-spec : #f | #t | ctor-name
          | (ctor-name field-name ...)

ctor-name : identifier
field-name : identifier

#fを指定すると、コンストラクタは生成されません。 #tを指定すると、デフォルトのコンストラクタがmake-type-name という名前で生成されます。 単一の識別子ctor-nameを指定すると、デフォルトのコンストラクタが、 その名前で生成されます。 デフォルトのコンストラクタは、レコードのフィールドの数と同じ数の引数を取ります。 これには(もしあれば)継承されたものも含まれます。 これが呼ばれると、レコードのインスタンスを割り当て、そのフィールドを 与えられた引数で順番に(継承されたフィールドが先になります)初期化し、 作成したレコードを返します。

ctor-specの最後のバリエーションは、 ctor-nameという名前のカスタムコンストラクタを生成します。 カスタムコンストラクタは、指定したfield-nameの数と同じ数の引数を取り、 その名前のフィールドを初期化します。 継承されたレコード型が祖先のレコード型と同名のフィールドを持っていた場合には、 継承されたレコード型のものだけが初期化されます。 Gaucheでは、初期化されていないフィールドは、何らかの値がセットされるまでは、 未束縛のままです。

pred-specは、レコードのインスタンスの述語を定義します。 それはひとつの引数を取り、定義されるレコード型、もしくは、その子孫の インスタンスであるときに#tを、そうでなければ#fを返します。

pred-spec : #f | #t | pred-name

pred-name : identifier

#fを指定すると、述語は生成されません。 #tを指定すると、述語がtype-name?という名前で生成されます。 単一の識別子を指定すると、述語がその名前で生成されます。

残りの引数は、レコードのフィールド(スロット)を定めます。

field-spec
 : field-name   ; immutable, with default accessor
 | (field-name) ; mutable, with default accessor/modifier
 | (field-name accessor-name); immutable
 | (field-name accessor-name modifier-name); mutable

field-name    : identifier
accessor-name : identifier
modifier-name : identifier

1番目と3番目の形式は、変更不可のフィールドを定義します。 それは、コンストラクタによる初期化のみが可能であり、 後から変更することはできません (したがって、そのフィールドは、モディファイアを持ちません)。 2番目と4番目の形式は、変更可能なフィールドを定義します。

3番目と4番目の形式は、アクセサとモディファイアの名前を明示的に指定します。 一方、1番目と2番目の形式では、 アクセサはtype-name-field-nameという名前になり、 モディファイアはtype-name-field-name-set!という 名前になります。

いくつかの例を見てみましょう。これは、レコード型pointの定義です。

(define-record-type point #t #t
  x y z)

変数pointは、レコード型記述子に束縛されています。 それは、単なるクラスです。しかし、さらにそのクラスを取得してみると、 実はメタクラス<record-meta>のインスタンスでもあることが分かります。

point            ⇒ #<class point>
(class-of point) ⇒ #<class <record-meta>>

デフォルトコンストラクタmake-pointを使用して、 pointのインスタンスを生成できます。 述語はデフォルトの名前であるpoint?になります。 そして、生成されたレコードのフィールドには、point-x等で アクセスできます。

(define p (make-point 1 2 3))

(point? p)  ⇒ #t
(point-x p) ⇒ 1
(point-y p) ⇒ 2
(point-z p) ⇒ 3

すべてのフィールドを変更不可として定義したため、 インスタンスpを変更することはできません。

以下は、pointの変更可能バージョンであるmpointです。 そのフィールドは、モディファイア手続き、および、 一般化されたset!によって変更することができます。

(define-record-type mpoint #t #t
  (x) (y) (z))

(define p2 (make-mpoint 1 2 3)) ; create an instance

(mpoint-x p2)  ⇒ 1

(mpoint-x-set! p2 4)            ; default modifier
(mpoint-x p2)  ⇒ 4

(set! (mpoint-x p2) 6)          ; generalized set! also works
(mpoint-x p2)  ⇒ 6

次は、継承の例です。 デフォルトコンストラクタは、親のレコードのフィールドの分も 引数に取ることに注意してください。

(define-record-type (qpoint mpoint) #t #t
  (w))

(define p3 (make-qpoint 1 2 3 4))

(qpoint? p3)  ⇒ #t      ; p3 is a qpoint
(mpoint? p3)  ⇒ #t      ; ... and also an mpoint

(mpoint-x p3) ⇒ 1       ; accessing inherited field
(mpoint-y p3) ⇒ 2
(mpoint-z p3) ⇒ 3
(qpoint-w p3) ⇒ 4

ちょっとした注意:継承されたフィールドのアクセサとモディファイア (例えば qpoint-x 等)は生成されません。

Gaucheの慣例では、クラス名は<>で囲うようにしています。 この慣例にしたがい、さらに各手続きについては (make-<point><point>-x) ではなく) シンプルな名前を使うようにするには、次のようにします。

(define-record-type <point> make-point point?
  (x point-x)
  (y point-y)
  (z point-z))

9.28.3 インスペクション層

この層は、レコード型記述子、および、レコードのインスタンスを操作する 共通手続きを提供します。

Gaucheでは、レコード型記述子はクラスであるため、クラスに対する操作 (例えば class-nameclass-slots 等)を、 レコード型記述子に対して使用することもできます。 しかし、これらの手続きは、よりポータブルです。

Function: record? obj

[SRFI-99][R6RS]{gauche.record} objがレコード型のインスタンスであれば#tを、 そうでなければ#fを返します。

Function: record-rtd record

[SRFI-99][R6RS]{gauche.record} レコードインスタンスのレコード型記述子を返します。

Function: rtd-name rtd

[SRFI-99]{gauche.record} レコード型記述子rtdの名前を返します。

Function: rtd-parent rtd

[SRFI-99]{gauche.record} レコード型記述子rtdの親の型を返します。 rtdが親を持たなければ#fが返ります。

Function: rtd-field-names rtd

[SRFI-99]{gauche.record} シンボルのベクタを返します。各シンボルは rtdで示されるレコードの直属のフィールドの名前です。 この結果には、継承されたフィールドは含まれません。

Function: rtd-all-field-names rtd

[SRFI-99]{gauche.record} シンボルのベクタを返します。各シンボルは rtdで示されるレコードのフィールドの名前です。 この結果には、すべての継承されたフィールドが含まれます。

Function: rtd-field-mutable? rtd field-name

[SRFI-99]{gauche.record} rtdで示されるレコードのfield-nameという名前を持つフィールドが、 変更可能であれば#tを、そうでなければ#fを返します。


9.28.4 手続き層

これらの手続きはdefine-record-typeを実装するための低レベルの機構です。 それらは実行時に新しいレコード型を作成するために使用できます。

Function: make-rtd name field-specs :optional parent

[SRFI-99]{gauche.record} nameという名前を持ちfield-specsで示されるフィールドを持つ 新しいレコード型記述子を作成して返します。 parentを指定した場合、それはレコード型記述子もしくは#fでなければなりません。 レコード型記述子であった場合、作成されるレコード型はそれを継承します。

field-specs引数は、field specifierを各要素に持つベクタ でなければりません。field specifierは、symbol(mutable symbol)というリスト、もしくは、 (immutable symbol)というリストのいずれかです。 symbolはフィールド名になります。symbolひとつ、もしくは、 (mutable symbol)形式の場合は、変更可能なフィールドを作ります。 そして、(immutable symbol)形式の場合は、 変更不可のフィールドを作ります。

注意: Gaucheは、SRFI-99で提案されている拡張機能である sealedopaque、および、uid引数を、まだ実装していません。

Function: rtd? obj

[SRFI-99]{gauche.record} objがレコード型記述子であれば#tを、 そうでなければ#fを返します。

Function: rtd-constructor rtd :optional field-specs

[SRFI-99]{gauche.record} rtdで示されるレコード型のインスタンスを作成する 手続きを返します。 field-specsがなければ、デフォルトコンストラクタを返します。 それは、初期化のためにレコードのフィールドの数と同じ数の引数を取ります。

field-specsには、シンボルのベクタを指定できます。 n番目のシンボルは、そのインスタンスのフィールドが、 n番目の引数で初期化されることを示します。 field-specsベクタは重複する名前を持つことはできません。 レコード型が親のレコード型と同じ名前のフィールドを定義した場合、 カスタムコンストラクタは継承先のフィールドのみを初期化します。

Function: rtd-predicate rtd

[SRFI-99]{gauche.record} オブジェクトがrtdのインスタンスであるかをテストする 述語を返します。

rtdが疑似レコード型であった場合、述語は、与えられたオブジェクトが、 内容を保持するのに十分なサイズを持つ適合した型かどうかをテストします。 詳しくは、疑似レコード型を参照してください。

Function: rtd-accessor rtd field-name

[SRFI-99]{gauche.record} 1個の引数としてrtdのインスタンスを取り、 そのインスタンスのfield-nameの値を返す 手続きを返します。

レコード型がfield-nameの名前のフィールドを持たない場合には、 エラーになります。

rtdが他のレコードを継承しており、 継承元と同名のフィールドを定義していた場合、この手続きが返すアクセサは、 継承先のレコードのフィールドの値を取得します。

Function: rtd-mutator rtd field-name

[SRFI-99]{gauche.record} 2個の引数としてrtdのインスタンスと値を取り、 そのインスタンスのfield-nameに値をセットする 手続きを返します。

レコード型がfield-nameの名前のフィールドを持たないか変更不可の場合には、 エラーになります。

rtd-accessorと同様に、 レコードが継承元と同名のフィールドを定義していた場合、 この手続きが返すモディファイアは、 継承先のレコードのフィールドのみを変更します。


9.28.5 疑似レコード型

疑似レコード型は、それ自身の型のインスタンスを作成しないレコード型です。 その代わり、ベクタのような他の集合型のオブジェクトを、 あたかもフィールド名を持っているかのように扱います。 例で理解するのが良いでしょう:

(define-record-type (vpoint (pseudo-rtd <vector>)) #t #t
  (x) (y) (z))

(make-vpoint 1 2 3)  ⇒ #(1 2 3)
(vpoint-x '#(1 2 3)) ⇒ 1

(rlet1 v (make-vpoint 1 2 3)
  (set! (vpoint-y v) -1))
 ⇒ #(1 -1 3)

疑似レコード型を作るには、他の疑似レコード型を親に指定します。 pseudo-rtd手続きは、適合するインスタンスクラスの ベース疑似レコード型を取得するために使用できます。

Function: pseudo-rtd instance-class

{gauche.record} instance-classを疑似レコードとして使うために 適合する疑似rtdを返します。

現在、<list><vector>、および、ユニフォームベクタ (例えば <u8vector> 等)が、 instance-classとしてサポートされています。

疑似レコード型の述語は、与えられたオブジェクトが 疑似レコード型として解釈できるときに#tを返します。 前述のvpointレコードの例の場合、述語vpoint?は、 3かそれ以上の要素を持つベクタのオブジェクトが与えられた場合に #tを返し、そうでなければ#fを返します。

(vpoint? '#(0 0 0))   ⇒ #t
(vpoint? '#(0 0))     ⇒ #f
(vpoint? '(0 0 0))    ⇒ #f
(vpoint? '#(0 0 0 0)) ⇒ #t

これは、より長いデータのヘッダ部分を解釈するために使えるように、 より多くの要素を許すようにしています。



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