レコードの初期化をカスタマイズする


Tags: R6RS, レコード

レコードの構築子をカスタマイズするには define-record-type に protocol 節を指定する。

protocol 節がない場合にはレコードの構築子は fields 節に指定されたフィールドの値を順に引数として取る手続きになる。

レコード型に parent 節がない場合、 protocol 節には、デフォルトの構築子手続きを受け取り所望の構築子手続きを返す手続きを指定すればよい。

例として、作成された時刻を記録しているレコードを考える。フィールドとしては名前とインスタンスの作成された時刻を設定するものとする。

(import (rnrs)
        (srfi :19))

(define-record-type person
  (fields
   (immutable name)
   (immutable birth-date)))

直截的にはインスタンスを生成するたびに現在の日時を渡してやればよい。

(define (make-person 'bob (current-date)))

(person-name bob) ; => bob
(person-birth-date bob) ; => #<record date 6880000 0 38 9 31 1 2009 32400>

しかしこれはインスタンス数が増えると手間なので自動化をしたい。

次のような補助手続きを使って書くこともできるが、

(define (make-person* name)
  (make-person name (current-date)))

protocol 節を使うと次のように書ける。

(import (rnrs)
        (srfi :19))

(define-record-type person
  (fields
   (immutable name)
   (immutable birth-date))
  (protocol
   (lambda (c)
     ;; This procedure becomes the actual constructor procedure.
     (lambda (name)
       (c name (current-date))))))

(define bob (make-person 'bob))

(person-name bob) ; => bob
(person-birth-date bob) ; => #<record date 6880000 0 38 9 31 1 2009 32400>

レコードに parent 指定のある場合は、 protocol 節に指定する手続きに渡される手続きが、親レコードの初期化を行ない現在のレコードのデフォルト構築子手続きを返す手続きになる。

上の例をレコードの拡張を使って書き直した例を下に示す。

(import (rnrs)
        (srfi :19))

(define-record-type person
  (fields
   (immutable name)))

(define-record-type person*
  (parent person)
  (fields
   (immutable birth-date))
  (protocol
   (lambda (c)
     (lambda (name)
       ((c name) (current-date))))))

(define bob (make-person* 'bob))

(person-name bob) ; => bob
(person*-birth-date bob) ; => #<record date 6880000 0 38 9 31 1 2009 32400>

protocol 節を指定したレコード型を拡張する場合には常にその子レコード型でも protocol を指定しなければならない。