R6RS:翻訳:Standard Libraries:6.2 Syntactic layer
6.2 構文レイヤ
構文レイヤは (rnrs records syntactic (6)) により提供される。仕様の詳細の一部は、下で述べる手続きレイヤの仕様で説明される。
構造体型定義フォーム define-record-type は定義であり、他の <definition> の現れられるところにならどこにでも現れることができる。
[syntax] (define-record-type <name spec> <record clause>*)
[auxiliary syntax] fields
[auxiliary syntax] mutable
[auxiliary syntax] immutable
[auxiliary syntax] parent
[auxiliary syntax] protocol
[auxiliary syntax] sealed
[auxiliary syntax] opaque
[auxiliary syntax] nongenerative
[auxiliary syntax] parent-rtd
define-record-type フォームは対応する構築子記述子に従って構造体型、構築子、述語、フィールドアクセサ、フィールド変更子を定義する。 define-record-type フォームはその現れた環境中に一連の定義として展開される。したがって、(構造体型自体の束縛を除いて)束縛群を再帰的に参照することができる。
<name spec> には構造体型、構築子、述語の名前を指定する。これは以下の形式のいずれかでなければならない。
(<record name> <constructor name> <predicate name>) <record name>
<record name>、 <constructor name>、 <predicate name> はすべて識別子でなければならない。
<record name> はシンボルとして解釈され、構造体型の名前になる(下の make-record-type-descriptor の説明を参照)。加えて、この定義により <record name> は展開時/実行時の構造体型の表現に束縛され、この定義を拡張する構文型の構造体定義の親構造体名として使うことができるようになる。また、基盤を為す構造体型記述子や構築子記述子にアクセスするのにも使うことができる(下の record-type-descriptor と record-constructor-descriptor を参照)。
<constructor name> は、 protocol 節で指定されたプロトコルに従って、構造体型の構築子として定義される。 protocol 節が省略された場合にはデフォルトのプロトコルが使われる。詳しくは、下の protocol 節の説明を参照。
<predicate name> は構造体型の述語として定義される。
<name spec> の二番目の形式は最初の形式の省略形である。構築子の名前は構造体の名前に make- を前置して生成され、述語の名前は構造体の名前の末尾に疑問符を付加することで生成される。例えば、構造体の名前が frob であるとき、構築子の名前は make-frob であり、述語の名前は frob? である。
各 <record clause> は以下の形式のひとつをとらなければならない。 define-record-type 内に同種の <record clause> が複数回現れるのは構文違反である。
(fields <field spec>*)
各 <field spec> の形式は次のいずれかである。
(immutable <field name> <accessor name>) (mutable <field name> <accessor name> <mutator name>) (immutable <field name>) (mutable <field name>) <field name>
<field name>、 <accessor name>、 <mutator name> はすべて識別子でなければならない。最初の形式は <field name> という変更不可能なフィールドと、それに対応する <accessor name> という名前のアクセサを宣言する。 2 番目の形式は<field name> という変更可能なフィールドと、それに対応する <accessor name> という名前のアクセサ、<mutator name> という名前の変更子を宣言する。
<field spec> が 3 番目か 4 番目の形式の場合、アクセサの名前は、ハイフンを区切りとして構造体の名前にフィールド名を連結したものになる。(変更可能なフィールドの)変更子の名前はアクセサの名前に -set! を後置したものになる。例えば、構造体の名前が frob であり、フィールドの名前が widget である場合、アクセサの名前は frob-widget になり、変更子は frob-widget-set! になる。
<field spec> が単に <field name> であった場合、それは (immutable <field name>) の略記であるものと解釈される。
<field name> は、順序を保ったまま、シンボルとして、作成される構造体記述子のフィールド名になる。
フィールド節は省略することもできる。この場合は空のフィールド節と同等になる。
(parent <parent name>)
構造体型に親型 <parent name> があることを指定する。 <parent name> は define-record-type を使って先に定義された構造体型の <record name> である。 <parent name> に対応する構造体型定義は凍結してはならない。 parent 節と parent-rtd 節(下記参照)のない構造体型は基底型である。
(protocol <expression>)
<expression> は define-record-type と同一の環境で評価される。また、定義中の構造体型に対して適切なものに評価されなければならない。
プロトコルは以下で述べる構造体構築子記述子を作成するのに使われる。 protocol 節が指定されない場合でも、構築子記述子はデフォルトのプロトコルを使って作成される。この節を省略できるのは、構造体型に親型がない場合か、親型の定義にプロトコルが指定されていない場合だけである。
(sealed #t)
(sealed #f)
このオプションに #t を指定すると、構造体型は凍結される。すなわち、この構造体型の拡張を作成できなくなる。#f を指定した場合や省略した場合には構造体型は凍結されない。
(opaque #t)
(opaque #f)
このオプションに #t を指定するか不透明な親構造体型が指定されていた場合には、定義される構造体型は不透明になる。さもなくは、構造体型は不透明にならない。詳しくは、下の record-rtd の仕様を参照。
(nongenerative <uid>)
(nongenerative)
構造体型が uid <uid> について生成的でないことを指定する。<uid> は識別子でなければならない。 <uid> が省略された場合には、マクロ展開時に一意な uid が生成される。ふたつの構造体型定義が同一の uid を指定した場合、構造体型定義は等価でなければならない。つまり、 make-record-type-descriptor に暗黙に渡される引き数が、 make-record-type-descriptor の条で述べるように等価でなければならないのである。これについてはR6RS:翻訳:Standard Libraries:6.3 Procedural layerを参照。この条件が満たされない場合、構文違反が起こるか、コンディション型 &assertion が通知され、例外が起こることが考えられる。条件が満たされている場合は両方の定義に対して単一の構造体型が生成される。
nongenerative 節が省略された場合、 define-record-type フォームが評価されるたびに新たに構造体型が生成される。
(let ((f (lambda (x) (define-record-type r ...) (if x r? (make-r ...))))) ((f #t) (f #f))) ⇒ #f
(parent-rtd <parent rtd> <parent cd>)
構造体型が <parent rtd> と <parent cd> で指定される親型を持つことを指定する。 <parent rtd> は構造体型記述子に評価される式で、 <parent cd> は構築子記述子に評価される式である(下記参照)。 <parent rtd> の値に対応づけられた構造体型定義は凍結されていてはならない。またさらに、構造体型定義には parent 節と parent-rtd 節の両方があってはならない。
注: 構文レイヤは展開時、すなわち define-record-type のマクロ定義により、親型が(もしあれば)既知の場合に、構造体インスタンスの大きさとフィールドのオフセットが決定できるように設計されている。このような利点を備える実装系は、 parent-rtd 節が使われた場合に効率の点で劣る構築子、アクセサ、変更子のコードを生成することがある。これは、一般に実行時まで親型を知ることができないからである。そのため、可能であれば代わりに parent 節を使うようにするべきである。
define-record-type により作成された束縛(構造体型、構築子、述語、アクセサ、変更子)は互いに異なる名前をしていなければならない。
define-record-type フォームによって作成された構築子は次のような手続きである。
- parent 節と protocol 節がない場合、構築子は存在するフィールドと同じ数の引き数を fields 節に現れた順序と同じ順序で受け取り、フィールドを対応する引き数で初期化した構造体オブジェクトを返す。
- parent ないし parent-rtd がなく、 protocol 節がある場合、 protocol 式は引き数をひとつ取る手続きに評価されなければならない。プロトコル手続きは define-record-type フォームの評価中に、手続き p を引き数に呼び出される。プロトコル手続きは手続きを返し、その手続きは <constructor name> に束縛される構築子になる。手続き p はフィールドと同じ数の引き数を fields 節に現れた順序と同じ順序で受け取り、フィールドを対応する引き数で初期化して構造体オブジェクトを返す。
プロトコル手続きの返した構築子手続きは任意個数の引き数を取り、 p を一度呼び出して構造体オブジェクトを構築子、そのレコードオブジェクトを返す。
例えば、次の 3 つのフィールドのある構造体型の定義のプロトコル手続きは、すべてのフィールドの値を受け取り、それを引き数とは逆の順序で初期化する構築子を作成する。
(lambda (p) (lambda (v1 v2 v3) (p v3 v2 v1)))
- parent 節と protocol 節の両方があった場合、プロトコル手続きが手続き n を引き数にして一度呼び出される。先の場合と同様に、プロトコル手続きは手続きを返し、それが <constructor name> に束縛される構築子になる。しかし、 n は先の p とは異なる。n は親型の構築子の引き数に対応する引き数を受け取る。そして、この型(で追加された)フィールドと同一個数の引き数を、 fields 節と同じ順序で受け取り、構造体オブジェクトを返す手続き p を返す。親構造体型はその構築子と n への引き数で初期化され、この構造体型のフィールドは p への引き数で初期化される。
プロトコル手続きの返した構築子は任意個数の引き数を取り、手続き p を構築するために n を一度呼び出す。そして、 p を一度呼び出し構造体オブジェクトを作成し、最終的に構造体オブジェクトを返す。
例えば、次のプロトコル式は親型の構築子が 3 つの引き数を取ることを仮定している。
(lambda (n) (lambda (v1 v2 v3 x1 x2 x3 x4) (let ((p (n v1 v2 v3))) (p x1 x2 x3 x4))))
結果の構築子は 7 つの引き数を取り、親型のフィールドを親型の構築子にしたがって v1、 v2、 v3 を引き数に初期化する。さらに、この構造体型のフィールドを x1、……、x4 の値で初期化する。 - parent 節があり、 protocol 節がない場合、親型に protocol 節自体があってはならない。 <constructor name> に束縛される構築子は親型の構築子に対応する引き数を最初に取り、fields 節の各フィールドを fields 節をそれぞれ順に受け取る。構築子は対応する引き数でフィールドが初期化された構造体オブジェクトを返す。
- parent-rtd 節がある場合、構築子は parent 節の場合と同様であるが、ただし、親型の構築子は parent-rtd 節の構築子記述子によって決定される。
プロトコルは上で述べた必要条件と一貫性のあるその他の動作をしてもかまわない。それには、新規構造体の変更やその他の副作用を構造体を返す前に行うことも含まれる。
暗黙の命名法を構築子、述語、アクセサ、変更子の名前に利用した定義は、簡単にすべての名前を明示的に指定した定義に簡単に書き直すことができる。例えば、暗黙の名前付けを使った構造体定義
(define-record-type frob (fields (mutable widget)) (protocol (lambda (p) (lambda (n) (p (make-widget n))))))
は、次の明示的な名前付けをつかった構造体定義と等価である。
(define-record-type (frob make-frob frob?) (fields (mutable widget frob-widget frob-widget-set!)) (protocol (lambda (p) (lambda (n) (p (make-widget n))))))
また、暗黙の名前付けを利用した定義
(define-record-type point (fields x y))
は次の明示的に名前付けを行った定義と等価である。
(define-record-type (point make-point point?) (fields (immutable x point-x) (immutable y point-y)))
暗黙の名前付けを行った場合でも、一部の名前を明示的につけることも可能である。例えば、下の例では widget フィールドのアクセサと変更子の名前を上書きしている。
(define-record-type frob (fields (mutable widget getwid setwid!)) (protocol (lambda (p) (lambda (n) (p (make-widget n))))))
[syntax] (record-type-descriptor <record name>)
<record name> で指定される型に対応づけられた構造体型記述子(下記参照)に評価される。
record-type-descriptor 手続きは不透明な構造体型にも不透明でない構造体型にも動作する。
[syntax] (record-constructor-descriptor <record name>)
<record name> で指定される型に対応づけられた構造体構築子記述子(下記参照)に評価される。
下記の例では (rnrs records inspection (6)) ライブラリ(R6RS:翻訳:Standard Libraries:6.4 Inspection)の record? 手続きを使っている。
(define-record-type (point make-point point?) (fields (immutable x point-x) (mutable y point-y set-point-y!)) (nongenerative point-4893d957-e00b-11d9-817f-00111175eb9e)) (define-record-type (cpoint make-cpoint cpoint?) (parent point) (protocol (lambda (n) (lambda (x y c) ((n x y) (color->rgb c))))) (fields (mutable rgb cpoint-rgb cpoint-rgb-set!))) (define (color->rgb c) (cons ’rgb c)) (define p1 (make-point 1 2)) (define p2 (make-cpoint 3 4 ’red)) (point? p1) ⇒ #t (point? p2) ⇒ #t (point? (vector)) ⇒ #f (point? (cons ’a ’b)) ⇒ #f (cpoint? p1) ⇒ #f (cpoint? p2) ⇒ #t (point-x p1) ⇒ 1 (point-y p1) ⇒ 2 (point-x p2) ⇒ 3 (point-y p2) ⇒ 4 (cpoint-rgb p2) ⇒ (rgb . red) (set-point-y! p1 17) ⇒ unspecified (point-y p1) ⇒ 17) (record-rtd p1) ⇒ (record-type-descriptor point) (define-record-type (ex1 make-ex1 ex1?) (protocol (lambda (p) (lambda a (p a)))) (fields (immutable f ex1-f))) (define ex1-i1 (make-ex1 1 2 3)) (ex1-f ex1-i1) ⇒ (1 2 3) (define-record-type (ex2 make-ex2 ex2?) (protocol (lambda (p) (lambda (a . b) (p a b)))) (fields (immutable a ex2-a) (immutable b ex2-b))) (define ex2-i1 (make-ex2 1 2 3)) (ex2-a ex2-i1) ⇒ 1 (ex2-b ex2-i1) ⇒ (2 3) (define-record-type (unit-vector make-unit-vector unit-vector?) (protocol (lambda (p) (lambda (x y z) (let ((length (sqrt (+ (* x x) (* y y) (* z z))))) (p (/ x length) (/ y length) (/ z length)))))) (fields (immutable x unit-vector-x) (immutable y unit-vector-y) (immutable z unit-vector-z))) (define *ex3-instance* #f) (define-record-type ex3 (parent cpoint) (protocol (lambda (n) (lambda (x y t) (let ((r ((n x y ’red) t))) (set! *ex3-instance* r) r)))) (fields (mutable thickness)) (sealed #t) (opaque #t)) (define ex3-i1 (make-ex3 1 2 17)) (ex3? ex3-i1) ⇒ #t (cpoint-rgb ex3-i1) ⇒ (rgb . red) (ex3-thickness ex3-i1) ⇒ 17 (ex3-thickness-set! ex3-i1 18) ⇒ unspecified (ex3-thickness ex3-i1) ⇒ 18 *ex3-instance* ⇒ ex3-i1 (record? ex3-i1) ⇒ #f