R6RS:翻訳:Standard Libraries:6.3 Procedural layer

R6RS:翻訳:Standard Libraries:6.3 Procedural layer

6.3 手続きレイヤ

手続きレイヤは (rnrs reocrds procedural (6)) ライブラリで提供される。

[procedure] (make-record-type-descriptor name parent uid sealed? opaque? fields)

組込み型すべてとその他の構造体型と区別のできる構造体型を表す構造体記述子(record-type-descriptor, rtd)を返す。

name 引き数はシンボルでなければならない。これは構造体型の名前であり、純粋に情報を提供する目的だけに使うことを意図してい、ベースにある Scheme システムが表示を行うときに利用することがある。

parent 引き数は #f か rtd でなければならない。 rtd である場合、戻り値の構造体型 tparent で表される親型 p を拡張したものになる。 parent が凍結されている場合はコンディション型 &asertion の例外が通知される(下記参照)。

uid 引き数は #f かシンボルでなければならない。 uid がシンボルである場合、構造体生成操作は非生成的である。すなわち、以前の make-record-type-descriptor の呼び出しでその uid を使っていなかった場合だけ、新しく構造体型を生成する。 #f であった場合は構造体生成操作は生成的であり、すなわち、以前に行われた make-record-type-descriptor の呼び出しがまったく同じ引き数であった場合でも新しい構造体型が生成される。

同一の uid シンボルを使って make-record-type を二回呼び出した場合、ふたつの呼び出しについて parent 引き数は eqv? でなければならず、 fields 引き数は equal?、 sealed? 引き数は真理値として等価(どちらも #f であるか真)で、 opaque? 引き数も真理値として等価でなければならない。これらの条件が満たされない場合、二度目の呼び出しの時点で &assertion コンディション型の例外が通知される。条件が満たされている場合は、二度目の呼び出しは、構造体型を生成せずに、最初の呼び出しと(eqv? の意味で)同一の構造体型記述子を返す。

: 利用者は uid 引き数に UUID 名前空間 [10] を使って構成したシンボル名を使うことが好ましい(例えば、構造体型の名前を接頭辞にする)。

sealed? フラグは真理値でなければならない。真の場合、戻り値の構造体型は凍結され、拡張することができない。

opaque? フラグは真理値でなければならない。真である場合、構造体型は不透明である。不透明な構造体型のインスタンスを渡した場合、 record? は #f を返す。さらに、 rcord-rtd (下記 R6RS:翻訳:Standard Libraries:6.4 Inspection 参照)を不透明な構造体型のインスタンスについて呼び出すと &assertion コンディション型の例外が発生する。親として不透明な構造体型が渡された場合も、構造体型は不透明になる。 opaque? が #f であり、親が不透明でない場合、生成される構造体は不透明ではない。

fields 引き数はフィールド指定子のベクタでなければならない。各フィールド指定子はリスト (mutable name) ないしは (immutable name) でなければならない。 name はシンボルであり、構造体型の対応するフィールドの名前になる。名前群は重複があってもかまわない。変更可能と指定されたフィールドは変更が可能であるのに対し、変更不可能と指定されたフィールドの変更子を取得しようとした場合、 &assertion コンディション型の例外が発生する。フィールドの順序には意味があり、構造体の構成やフィールドへのアクセスでは指定された通りの順序に並んでいるものと考えられるが、実際の構造体インスタンスの表現については特定の順序をつける必要はない。

指定したフィールドは親にフィールドがあれば、そこに追加され、戻り値の構造体型のフィールドの完全な集合が決定される。make-record-type-descriptor を呼び出したあとにフィールド群を変更した場合の戻り値の rtd への効果は未定義である。

make-record-type-descriptor を呼び出しによって生成された生成的なレコード型は、ほかの make-record-type-descriptor の呼び出しによって生成された構造体型記述子とは(生成的なものでも非生成的なものであっても) eqv? ではない。生成的な構造体記述子はそれ自体のとみ eqv? である。すなわち、 (eqv? rtd1 rtd2) であるのは (eq? rtd1 rtd2) の場合であり、かつその場合だけである。また、非生成的な構造体型記述子ふたつが eqv? であるのは、それらが同一の uid 引き数とともに make-record-descriptor を呼び出して生成された場合であり、かつその場合だけである。

[procedure] (record-type-descriptor? obj)

引き数が構造体記述子である場合だけ #t を返し、それ以外は #f を返す。

[procedure] (make-record-constructor-descriptor rtd parent-constructor-descriptor protocol)

構造体構築子(あるいは略して構築子)を指定する構造体構築子記述子(あるいは略して構築子記述子)を返す。構築子は rtd で指定された構造体の値を生成するのに使うことができ、 record-constructor を使って取り出すことができる。構築子記述子は自身の構造体型の下位型の構築子記述子を作成するのにも使うことができる。 rtd は構造体型記述子でなければならない。 protocol は手続きか #f でなければならない。 #f であった場合にはデフォルトのプロトコル手続きが渡される。

protocol は、手続きであった場合には define-record-type フォームの場合と同様に取り扱われる。

rtd が基底構造体型であった場合、 parent-constructor-descriptor は #f でなければならない。この場合 protocol はひとつの引き数 p とともに構造体記述子から呼び出される。 prtd の各フィールドに対してひとつの引き数を取り、 rtd のそのフィールドが指定された引き数に初期化された構造体を返す。 protocol の返す手続きは引き数群とともに p を一度呼び出し、下の簡単な例に示すように、戻り値として構造体を返す。

(lambda (p)
  (lambda (v1 v2 v3)
    (p v1 v2 v3)))

ここでは、フィールドが v1、 v2、 v3 で初期化し構造体を返す手続き p を呼び出している。上の式は (lambda (p) p) と等価である。 protocol の返す手続きにはそれ以外何の制約もないことに注意。特に、引き数をいくつ取ってもかまわない。

rtd が他の構造体型 parent-rtd の拡張で、protocol が手続きであった場合、 parent-constructor-descriptorparent-rtd の構築子記述子であるか #f でなければならない。 parent-constructor-descriptor が構築子記述子である場合、 protocol はひとつの引き数 n をともなって record-constructor から呼び出される。 n は parent-constructor-descriptor の構築子と同一個数の引き数を取り、手続き p を返す。 p を呼び出すと、構造体自身が構成される。手続き prtd の各フィールドごとに引き数をひとつ取り(親のフィールドは含まない)、 rtd のフィールドがこれらの引き数に初期化された構造体を返し、 parent-rtd のフィールドと親構造体は parent-constructor-descriptor に指定された通りに初期化される。

protocol の返した手続きは n の期待する数の引き数とともに n を一度呼び出し、 n の返した手続き pp の期待する数の引き数とともに一度呼び出し、結果の構造体を返す。この場合の簡単なプロトコルは次のように書くことができる。

(lambda (n)
  (lambda (v1 v2 v3 x1 x2 x3 x4)
    (let ((p (n v1 v2 v3)))
      (p x1 x2 x3 x4))))

これは、引き数 v1v2v3 を parent-constructor-descriptor に渡し、 p を x1、……、x4 とともに呼び出し、 rtd 自身のフィールドを初期化している。

したがって、ある構造体型の構造体型記述子はレコード型の親子関係の列と平行なプロトコルの列をかたちづくっている。この列中の各構築子記述子は対応する構造体型のフィールドの値を決定づける。子構造体構築子は親構造体のフィールドの内容の個数を知る必要はなく、親構造体の構築子の受け付ける引き数の個数だけを知っていればよい。

protocol は #f であってもよい。この場合は、デフォルトの構築子が設定され、 rtd の各フィールドに対して引き数を受け付ける(親型のある場合はそのフィールドも含む)。厳密に言うと、 rtd が基底型である場合、 protocol 手続きは (lambda (p) p) のように振舞う。 rtd が他の型の拡張である場合、 parent-constructor-descriptor は #f とするか、それ自身デフォルトの構築子を指定しなければならない。デフォルトの protocol 手続きは次のように動作する。

(lambda (n)
  (lambda (v1 ... vj x1 ... xk)
    (let ((p (n v1 ... vj)))
      (p x1 ... xk))))

結果の構築子は、構造体型のフィールドの完全な集合のそれぞれについて引き数をひとつ取り(親構造体型、親の親の構造体型……のものも含む)、フィールドが引き数の値に初期化された構造体を返す。このとき、親構造体のフィールドの値は、引き数リスト内で拡張する側の構造体の値よりも先に来る(例えば、 j が親型の構造体のフィールドの個数であった場合、 krtd 自身のフィールドの個数である)。

rtd が他の構造体の拡張であり、parent-constructor-descriptor が #f であった場合、 parent-constructor-descriptor は既定のプロトコルを持った rtd の親 rtd の構築子記述子であるかのように扱われる。

実装系の義務: protocol が手続きの場合、実装系は must check the restrictions on it to the extent performed by applying it as described when the constructor is called. 実装系は protocol の適用前に適切な引き数が渡されているか確認してもよい。

(define rtd1
  (make-record-type-descriptor
   ’rtd1 #f #f #f #f
   ’#((immutable x1) (immutable x2))))

(define rtd2
  (make-record-type-descriptor
   ’rtd2 rtd1 #f #f #f
   ’#((immutable x3) (immutable x4))))

(define rtd3
  (make-record-type-descriptor
   ’rtd3 rtd2 #f #f #f
   ’#((immutable x5) (immutable x6))))

(define protocol1
  (lambda (p)
    (lambda (a b c)
      (p (+ a b) (+ b c)))))

(define protocol2
  (lambda (n)
    (lambda (a b c d e f)
      (let ((p (n a b c)))
        (p (+ d e) (+ e f))))))

(define protocol3
  (lambda (n)
    (lambda (a b c d e f g h i)
      (let ((p (n a b c d e f)))
        (p (+ g h) (+ h i))))))

(define cd1
  (make-record-constructor-descriptor
    rtd1 #f protocol1))

(define cd2
  (make-record-constructor-descriptor
    rtd2 cd1 protocol2))

(define cd3
  (make-record-constructor-descriptor
    rtd3 cd2 protocol3))

(define make-rtd1 (record-constructor cd1))

(define make-rtd2 (record-constructor cd2))

(define make-rtd3 (record-constructor cd3))

(make-rtd3 1 2 3 4 5 6 7 8 9)
                ⇒
  〈3, 5, 9, 11, 15, 17 に初期化されたフィールドを持つ構造体〉

[procedure] (record-constructor constructor-descriptor)

(make-record-constructor-descriptor で述べた)constructor-descriptorprotocol を呼び出し、 constructor-descriptor に対応する構造体型の構築子 constructor を結果として返す。

[procedure] (record-predicate rtd)

オブジェクト obj が与えられたときに、objrtd で表される型の構造体である場合 #t を返し、それ以外の場合には #f を返す関数を返す。

[procedure] (record-accessor rtd k)

krtd のフィールドの添字番号として適切なものでなければならない。 record-accessor は rtd で表される型の構造体をひとつ引き数に取る一引き数の手続きを返す。この手続きは、その構造体の選択されたフィールドの値を返す。

選択されたフィールドは、 rtd を作成した make-record-type-descriptor の呼び出しの引き数の k 番目の要素(0 始まり)に対応する。 krtd の拡張した型のフィールドを指定するのに使うことのできないことに注意。

[procedure] (record-mutator rtd k)

krtd の適切なフィールド添字番号でなければならない。 record-mutator は rtd の表す構造体 r オブジェクト obj のふたつの引き数を取る手続きを返す。この手続きは r のフィールドの k で指定されたものに obj を格納すう。引き数 k の意味については record-accessor の場合と同様である。 k で変更不可能なフィールドを指定した場合はコンディション型 &assertion の例外が発生する。変更子の戻り値は基底されていない。

(define :point
  (make-record-type-descriptor
    ’point #f
    #f #f #f 
    ’#((mutable x) (mutable y))))

(define :point-cd
  (make-record-constructor-descriptor :point #f #f))

(define make-point (record-constructor :point-cd))

(define point? (record-predicate :point))
(define point-x (record-accessor :point 0))
(define point-y (record-accessor :point 1))
(define point-x-set! (record-mutator :point 0))
(define point-y-set! (record-mutator :point 1))

(define p1 (make-point 1 2))
(point? p1)         ⇒ #t
(point-x p1)         ⇒ 1
(point-y p1)         ⇒ 2
(point-x-set! p1 5)         ⇒ unspecified
(point-x p1)         ⇒ 5

(define :point2
  (make-record-type-descriptor
    ’point2 :point 
    #f #f #f ’#((mutable x) (mutable y))))

(define make-point2
  (record-constructor
    (make-record-constructor-descriptor :point2
      #f #f)))
(define point2? (record-predicate :point2))
(define point2-xx (record-accessor :point2 0))
(define point2-yy (record-accessor :point2 1))

(define p2 (make-point2 1 2 3 4))
(point? p2)         ⇒ #t
(point-x p2)         ⇒ 1
(point-y p2)         ⇒ 2
(point2-xx p2)         ⇒ 3
(point2-yy p2)         ⇒ 4

(define :point-cd/abs
  (make-record-constructor-descriptor
   :point #f
   (lambda (new)
     (lambda (x y)
       (new (abs x) (abs y))))))

(define make-point/abs
  (record-constructor :point-cd/abs))

(point-x (make-point/abs -1 -2))
                ⇒ 1
(point-y (make-point/abs -1 -2))
                ⇒ 2

(define :cpoint
  (make-record-type-descriptor
   ’cpoint :point
   #f #f #f
   ’#((mutable rgb))))

(define make-cpoint
  (record-constructor
   (make-record-constructor-descriptor
    :cpoint :point-cd
    (lambda (p)
      (lambda (x y c)
        ((p x y) (color->rgb c)))))))

(define make-cpoint/abs
  (record-constructor
   (make-record-constructor-descriptor
    :cpoint :point-cd/abs
    (lambda (p)
      (lambda (x y c)
        ((p x y) (color->rgb c)))))))

(define cpoint-rgb
  (record-accessor :cpoint 0))

(define (color->rgb c)
  (cons ’rgb c))

(cpoint-rgb (make-cpoint -1 -3 ’red))
                ⇒ (rgb . red)
(point-x (make-cpoint -1 -3 ’red)) 
                ⇒ -1
(point-x (make-cpoint/abs -1 -3 ’red)) 
                ⇒ 1
More ...