表題

レコード型の定義

著者

Richard Kelsey

状態

この SRFI は現在「確定」の状態である。 SRFI の各状態の説明については ここ を参照せよ。 この SRFI に関する議論については メーリングリストのアーカイブ を参照せよ。

概要

この SRFI では、レコード型と呼ばれる新しいデータ型を作成する構文について述べる。 各レコード型に対して、述語、コンストラクタ、フィールド アクセッサ、フィールド変更子を定義する。 新しいレコード型は、既存の型とはまったく異なる型であり、他のレコード型や Scheme 定義済み型とも異なる型である。

論拠

多くの Scheme 実装系には新しい型を定義するための方法があり、それは通常、レコードまたは構造体と呼ばれている。 この文書で述べる DEFINE-RECORD-TYPE 構文は、Jonathan Rees による Scheme 48 のために書かれた構文を少々シンプルにしたものである。 レコードを定義するための多くのマクロ (特殊フォーム) とは異なり、 この構文では新しい識別子を作成せず、 その代わり、レコード型、述語、コンストラクタなどの名前はソースに明示的に記述される。 これには以下のような利点がある。

仕様

レコード型定義の構文を以下に示す。
 <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 のインスタンスは以下の定義に等価である。

レコード型は 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

実装

このコードは 3 層になっている。 上から下の順に説明すると、以下のようになる。
  1. DEFINE-RECORD-TYPE のための構文定義および補助マクロ。
  2. 手続き的インタフェースによるレコード型の実装。 Scheme 実装系によってはすでにこれに似たものを実装しているものもある。
  3. R5RS で定義されているベクタのようなレコード。 これは標準 Scheme のいくつか手続きを再定義するので、 他のコード (上記の第 2 層のコードを含む) の前にロードする必要がある。 これらの手続きはレコード型の抽象化を破壊することに注意せよ (たとえば、レコードの型を変更するために 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.


編集者: Mike Sperber