DEFINE-RECORD-TYPE
構文は、Jonathan Rees による Scheme 48
のために書かれた構文を少々シンプルにしたものである。
レコードを定義するための多くのマクロ (特殊フォーム) とは異なり、
この構文では新しい識別子を作成せず、
その代わり、レコード型、述語、コンストラクタなどの名前はソースに明示的に記述される。
これには以下のような利点がある。
SYNTAX-RULES
マクロで定義することができ、
レコード型を作成するための手続き的なインタフェースを与えることができる。
grep
や GNU Emacs のタグ機能のようなツールにより、
識別子の定義場所を簡単に特定できる。
<command or definition> -> <record type definition> ; R5RS の 8.1.6 に追加 <record type definition> -> (define-record-type <type name> (<constructor name> <field tag> ...) <predicate name> <field spec> ...) <field spec> -> (<field tag> <accessor name>) -> (<field tag> <accessor name> <modifier name>) <field tag> -> <identifier> <... name> -> <identifier>
DEFINE-RECORD-TYPE
は生成的な機能をもつ。
つまり、これを使用するたびに新しいレコード型が作成される。
それは既存のどの型とも異なり、他のレコード型や Scheme 定義済み型とも異なる。
レコード型はトップレベルでのみ定義することができる
(レコード型定義の内部では、生成的および非生成的な
2 つのセマンティクスが考えられるが、
そのどちらが優れているかについては賛否両論があるだろう) 。
DEFINE-RECORD-TYPE
のインスタンスは以下の定義に等価である。
<type name>
はレコード型それ自体の表現に束縛される。
表示メソッドの定義、リフレクションなどのレコード型の操作については、
別の SRFI で規定されることを望む。
<constructor name>
は手続きに束縛される。
その手続きは、
(<constructor name> ...)
サブフォーム内の
<field tag>
の個数に等しい数の引数をとり、
新しい <type name>
レコードを返す。
<constructor name>
に指定されるフィールドは、
その初期値に対応する引数をもつ。
それ以外のフィールドの初期値は不定値である。
<predicate name>
は述語であり、
<constructor name>
により返される値に対して #T を返し、
それ以外の値に対して #F を返す。
<accessor name>
は手続き (アクセッサ) であり、
<type name>
型のレコードを引数にとり、
それに対応するフィールドの値を返す。
この手続きに適切でないレコード型の値を渡すことはエラーである。
<modifier name>
は手続き (変更子) であり、
<type name>
型のレコードと
フィールドに設定する新しい値を引数にとり、
不定値を返す。
変更子の 1 つめの引数に適切でないレコード型を渡すことはエラーである。
レコード型は R5RS のセクション 4.2 に記述されている型とはまったく別の型である。
これらの識別子に set!
で値を設定しても、
元の値の動作は影響を受けないものとする。
次の定義を見てみよう。
(define-record-type :pare (kons x y) pare? (x kar set-kar!) (y kdr))ここでは、
KONS
がコンストラクタ、KAR
および KDR
がアクセッサ、SET-KAR!
が変更子、PARE?
:PARE
を判定する述語となっている。
(pare? (kons 1 2)) --> #t (pare? (cons 1 2)) --> #f (kar (kons 1 2)) --> 1 (kdr (kons 1 2)) --> 2 (let ((k (kons 1 2))) (set-kar! k 3) (kar k)) --> 3
DEFINE-RECORD-TYPE
のための構文定義および補助マクロ。
RECORD-SET!
を使用することができる)。
これらの手続きへのアクセスには制限をかけるべきである。
; DEFINE-RECORD-TYPE の定義 (define-syntax define-record-type (syntax-rules () ((define-record-type type (constructor constructor-tag ...) predicate (field-tag accessor . more) ...) (begin (define type (make-record-type 'type '(field-tag ...))) (define constructor (record-constructor type '(constructor-tag ...))) (define predicate (record-predicate type)) (define-record-field type field-tag accessor . more) ...)))) ; フィールド アクセッサおよび変更子を定義するための補助マクロ。 ; 変更子がオプションであるために必要となる。 (define-syntax define-record-field (syntax-rules () ((define-record-field type field-tag accessor) (define accessor (record-accessor type 'field-tag))) ((define-record-field type field-tag accessor modifier) (begin (define accessor (record-accessor type 'field-tag)) (define modifier (record-modifier type 'field-tag))))))
; 次の手続きを定義する。 ; ; (make-record-type <type-name <field-names>) -> <record-type> ; (record-constructor <record-type<field-names>) -> <constructor> ; (record-predicate <record-type>) -> <predicate> ; (record-accessor <record-type <field-name>) -> <accessor> ; (record-modifier <record-type <field-name>) -> <modifier> ; ここで ; (<constructor> <initial-value> ...) -> <record> ; (<predicate> <value>) -> <boolean> ; (<accessor> <record>) -> <value> ; (<modifier> <record> <value>) -> <unspecific> ; レコード型はベクタのようなレコードを使用して実装される。 ; 各レコードの最初のスロットはレコードの型を含み、 ; それはそれ自体が 1 つのレコードである。 (define (record-type record) (record-ref record 0)) ;---------------- ; レコード型はそれ自体レコードであり、まずそのための定義を行う必要がある。 ; 循環参照が起きていることを除けば、次のように定義できるだろう。 ; (define-record-type :record-type ; (make-record-type name field-tags) ; record-type? ; (name record-type-name) ; (field-tags record-type-field-tags)) ; これと同じことを、すべて手書きで定義する必要がある。 (define :record-type (make-record 3)) (record-set! :record-type 0 :record-type) ; 型はそれ自身である (record-set! :record-type 1 ':record-type) (record-set! :record-type 2 '(name field-tags)) ; :record-type が定義できたので、他のレコード型を作成するための手続きを定義する。 (define (make-record-type name field-tags) (let ((new (make-record 3))) (record-set! new 0 :record-type) (record-set! new 1 name) (record-set! new 2 field-tags) new)) ; レコード型のアクセッサ (define (record-type-name record-type) (record-ref record-type 1)) (define (record-type-field-tags record-type) (record-ref record-type 2)) ;---------------- ; レコード内のフィールドのオフセットを取得するためのユーティリティ (define (field-index type tag) (let loop ((i 1) (tags (record-type-field-tags type))) (cond ((null? tags) (error "record type has no such field" type tag)) ((eq? tag (car tags)) i) (else (loop (+ i 1) (cdr tags)))))) ;---------------- ; 以上の定義が終ると、DEFINE-RECORD-TYPE のマクロ展開で使用される ; RECORD-CONSTRUCTOR および残りの手続きを定義することができる。 (define (record-constructor type tags) (let ((size (length (record-type-field-tags type))) (arg-count (length tags)) (indexes (map (lambda (tag) (field-index type tag)) tags))) (lambda args (if (= (length args) arg-count) (let ((new (make-record (+ size 1)))) (record-set! new 0 type) (for-each (lambda (arg i) (record-set! new i arg)) args indexes) new) (error "wrong number of arguments to constructor" type args))))) (define (record-predicate type) (lambda (thing) (and (record? thing) (eq? (record-type thing) type)))) (define (record-accessor type tag) (let ((index (field-index type tag))) (lambda (thing) (if (and (record? thing) (eq? (record-type thing) type)) (record-ref thing index) (error "accessor applied to bad value" type tag thing))))) (define (record-modifier type tag) (let ((index (field-index type tag))) (lambda (thing value) (if (and (record? thing) (eq? (record-type thing) type)) (record-set! thing index value) (error "modifier applied to bad value" type tag thing)))))
; ここでは、ベクタに等価なレコード抽象化を実装する。 ; ただし、それはベクタではない (VECTOR? はレコードに対して偽を返し、 ; RECORD? はベクタに対して真を返す)。 ; 次の手続きが定義される。 ; (record? <value>) -> <boolean> ; (make-record <size>) -> <record> ; (record-ref <record> <index>) -> <value> ; (record-set! <record> <index> <value>) -> <unspecific> ; これらの手続きは R5RS Scheme の範囲内でベクタとして実装できる。 ; インデックス 0 に識別値を格納し、VECTOR? はその引数が識別値を含むときに ; 偽を返すように再定義する。そして、EVAL が VECTOR? の新しい定義を ; 使用するように再定義する。 ; マーカーを定義し、VECTOR? と EVAL を再定義する。 (define record-marker (list 'record-marker)) (define real-vector? vector?) (define (vector? x) (and (real-vector? x) (or (= 0 (vector-length x)) (not (eq? (vector-ref x 0) record-marker))))) ; これは、対話的環境において LAMBDA が再定義されたときには動作しない。 (define eval (let ((real-eval eval)) (lambda (exp env) ((real-eval `(lambda (vector?) ,exp)) vector?)))) ; レコード手続きの定義 (define (record? x) (and (real-vector? x) (< 0 (vector-length x)) (eq? (vector-ref x 0) record-marker))) (define (make-record size) (let ((new (make-vector (+ size 1)))) (vector-set! new 0 record-marker) new)) (define (record-ref record index) (vector-ref record (+ index 1))) (define (record-set! record index value) (vector-set! record (+ index 1) value))
Copyright (C) Richard Kelsey (1999). All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.