ERR5RS Records
William D Clinger
This SRFI is currently in ``final'' status. To see an explanation of
each status that a SRFI can hold, see here.
To provide input on this SRFI, please
mail to
<srfi minus 99 at srfi dot schemers dot org>
. See
instructions here to subscribe to
the list. You can access previous messages via
the archive of the mailing list.
多くの Scheme プログラマが、レコードは R5RS に欠けている最も重要な機能のひとつであると考えてきた。 R6RS ではレコードシステムが提案されたが、その設計は多くの人に批判され、またともかくも R5RS プログラムで使うことを意図していない。
本 SRFI は R5RS、 ERR5RS、 R6RS から利用可能な、よりよいレコードシステムを提案する。 本 SRFI のレコードシステムの構文レイヤは SRFI 9 の拡張版であり、 手続きレイヤと検査レイヤは構文レイヤと完全互換である。 本 SRFI は全体として R6RS レコードシステムの手続きレイヤ、調査レイヤと互換であるが、 R6RS のシステムに対してさまざまな改良をほどこしている。
ほとんどのプログラミング言語でレコード(または構造体、クラス)は、異なる型の値をひとつのオブジェクトにまとめるものとして重要である。
そのような機能を提供するものとしては Scheme には既にベクタと手続きがあるが、レコードは以下のふたつの点において依然として重要である。
多くの人にとってレコードは R6RS で導入されたもののうち最も重要な機能であるが、 R6RS で実際に提案されたレコードシステムは多くの人に批判されてきた。 R6RS の承認に反対票を投じた 30% の人は、その理由のひとつとしてレコードシステムを挙げている[1]。
本 SRFI で述べる ERR5RS レコードシステムは R6RS よりも単純で、完全に可搬な代替案を提案する。 ERR5RS レコードシステムは以下の要素から成る。
ERR5RS レコードシステムでは、 R6RS レコードシステムの非生成的なレコードや閉じたレコード、不透明なレコードをサポートすることを求めないが、 本 SRFI をサポートする実装系は ERR5RS レコードシステムを拡張し、それらの機能をサポートしてもよいし、そうすることを推奨する。 そのような拡張をほどこせば、 ERR5RS レコードシステムは R6RS レコードシステムと同じ表現力を有することになる。 したがって、本 SRFI のレコードシステムは以下の両方またはいずれかと考えることができる。
本節の残りでは以下のようなことを考慮に入れつつ本 SRFI の動機について説明する。
Scheme にレコードを加えることの重要性は 20 年以上前から認識されていた[2]。
SRFI 9 と R6RS レコードシステムの背後にある考え方の基本は
Norman Adams が 1987 年 7 月 8 日にまとめている[3]。
これと同じようなものは T や MIT CScheme で実装されていた。
Jonathan Rees が 1988 年 5 月 26 日に Adam の提案の改訂版を投稿し[4]、
Pavel Curtis が Rees の提案の拡張版を 1989 年 8 月 18 日に提案した。
この提案が BASH (Bay Area Scheme Hackers?)の最初の会合で合意を得られているというのは注目に値する[5]。
rrrs-authors
アーカイブに収められている、この提案に対する意見の数々は一読の価値がある。
Rees と Curtis の提案は 1992 年に改訂された[6]。 1992 年 7 月 25 日に RnRS の著者が会合を開いたときには、この提案にはまだ議論の余地があるという印象だった[7]。 Kent Dybvig はこの提案に対して様々な点で反論を述べた。 調査レイヤを用意している点、変更不可能なレコードを定義できない点、スペシャルフォームではなく手続きを使っている点などである。 この提案に対しては 9 人が採択の意図を示したが、 11 人が反対した。
1996 年 4 月 23 日、 Bruce Duba、 Matthew Flatt、 Shriram Krishnamurthi の三人がレコードについての議論を再び俎上に上げた[8]。 Alan Bawden と Richard Kelsey は Duba/Flatt/Krishnamurthi による提案は、本質的に Pavel Curtis の提案と同じものであるとし、 Kelsey は Curtis の提案を再投稿した。 Kent Dybvig は以前と同じ 3 つの点において反対し、また、手続き的なインタフェースは効率的にコンパイルすることが難しく、そのために可搬性の問題が発生することについて論じた[9]。
そのような議論があるものの、実際には、手続き的インタフェースにより非効率的になるということはない。
現在では、生成的なレコードに関しては、構文的なインタフェースによる利点はないということで合意が得られている[10]。
非生成的なレコードに対しても、主張されている非効率性はロード命令ひとつ分にすぎず、これについても、同一のレコードに連続してアクセスするときは、最初のもの以外は、ロード命令を含む実行時チェックとともに、最適化コンパイラで取り除くことができる(この最適化はリストの car
を計算した状態で cdr
を計算するときに、ペアであるか検査するのを省く最適化の素直な拡張である)。
さらに、連続的でないアクセスについても、手続き的インタフェースで非効率性を除くのは、構文的インタフェースの場合と比べて困難とは言えないことがわかっている[11]。
したがって、 R6RS ライブラリの 6 章で構文レイヤの優位性を主張している文はどちらも根拠のないものなのである
(その文というのは、 6.1 節手前の後ろから 2 番目の段落と、
parent-rtd
の説明の注の部分にある)。
1996 年 4 月 24 日、 Bill Rozas が、同一のレコードシステムに対して、手続き的なものと構文的なもののふたつの API を持たせることを提案した[12]。 二日後、 Dybvig は、いくつか不自然な制限を加えた妥協案を提案した[13]。 これは明かに、あの疑わしい余分なロード命令問題を心配してのものであった[14]。 Dybvig と Rozas はこの提案を煮詰め続け、 1998 年の Scheme Workshop の後でその概要を発表した[15]。 筆者は、この提案を印刷したものやオンライン版を見つけられていない。
Richard Kelsey が 1999 年 7 月に提案した SRFI 9 は Rees、 Curtis、 Duba/Flatt/Krishnamurthi の後裔につらなる構文的 API である[16]。
単一継承の機能は、 1998 年に Larceny で、 1999 に Chez Scheme で追加された[17]。
Andre van Tonder が 2004 年 9 月に提案した SRFI 57 にはラベル多相機能がある。 これは、ある種の構造的部分型と多重継承と考えることができる[18]。
R6RS は構文・手続き・調査の 3 層から成る、単一継承のシステムを提案した[19]。
R6RS の手続きレイヤは、一般に、継承の各段階について少なくとも、レコード型記述子、レコード構築子記述子、実構築子(そのレコード型のインスタンスを作る場合)のみっつの定義を別個に行なう必要がある。
(承認されていない) R6RS の論拠[20]では構築子記述子の仕組みについて、 「単にフィールドの初期値を取るデフォルトの構築子を作るだけでなく、特殊化した構築子を作るための基盤である。 これによって、継承の各階層でフィールドを制御できるようにしつつ子レコードの定義では親のフィールドがどのようになってい、いくつあるかを捨象することができる」 と述べている。 (承認された) R6RS のライブラリドキュメントでも(承認されていない)論拠でも、構築子記述子の仕組みにより、構築子を特殊化しない場合という、ごく一般的な場合に無用の複雑さを持ち込んでいることを考慮していない。 さらに、下の最初の例に示すように、構築子を特定化する必要がある場合でも構築子記述子を使うことによって得られる利点は小さいことも等閑にしている。
R6RS ライブラリ仕様のレコードの条では、レコード型は「レコード型記述子により規定され」、「レコード型記述子は、レコードのフィールドや、その型レコードすべてが持つ様々な性質を規定するオブジェクトである」としている[21]。 レコード型記述子はオブジェクトであるので、ライブラリの公開する変数の値とすることができる。 だがしかし、下で述べるとおり、 R6RS の構文レイヤの考え方はこれとは異なり、レコード型はオブジェクトでも構文でもないことがあると主張している。
R6RS の構文レイヤは SRFI 9 や SRFI 99 (本 SRFI)で定義されている
define-record-type
と同名であるが互換性のない構文により構成されている。
R6RS ライブラリ 6.2 節によれば、 R6RS の
define-record-type
では、レコード名は「レコード型の展開時ないしは実行時の表現」に束縛され、「定義済みのレコード型を拡張したレコード型を定義する構文で親レコードの名前として使うことができる。また、そのレコードのレコード型記述子や構築子記述子を取り出すために使うことができる」としている。
可搬性のあるコードでは、レコード名がレコード記述子に束縛されることを仮定してはならないことに注意してほしい。 可搬性のあるコードでは、レコード名は「展開時もしくは実行時の表現」に束縛されていることしか仮定できず、それ以外の意味は R6RS 本文や R6RS ライブラリドキュメントでは説明されていない。 特に、可搬性のあるコードではレコード名をライブラリから公開することはできない。 ライブラリはオブジェクトや構文に束縛された名前を公開することができるが、 R6RS ではレコード名の表現がそのどちらになるべきか述べていない。
レコード名を束縛するこの不思議な何かに対して
record-type-descriptor
構文や
record-constructor-descriptor
構文を使うと、それぞれレコード型記述子や構築子記述子を取り出すことができる。
こうして取り出したレコード型記述子や構築子記述子はライブラリから公開することができるものであり、これを公開することが
R6RS 構文レイヤで定義したレコード型を公開するための唯一可搬性のある方法であることは明らかである。
取り出したレコード型記述子や構築子記述子を使えば、構文レイヤで定義したレコード型を継承したレコード型を手続きレイヤで新たに定義することもできる。
同様に、構文レイヤでも parent-rtd
節を使えば手続きレイヤで定義したレコード型を継承したレコード型を新たに定義することができる。
とはいうものの、手続きレイヤと構文レイヤで使われているレコード型の概念は相互に取り替えることはできない。 どちらからどちらを継承する場合でも、プログラマは先に定義されたレコード型が手続きを使って定義されたのか構文を使って定義されたのかを知っていなければならないのである。 R6RS の手続きレイヤと構文レイヤが完全互換であるのなら、レコード型の定義を手続きによるものから構文によるもの(またはその逆)に変更しても、利用者にはそれがわからないはずである。 にも関わらず、 R6RS のレコードシステムはちょっとした変更でレコード型を継承しているコードが動かなくなるように定義されているのである。
R6RS ライブラリの 6 章では、この非互換性や、相互運用性・保守上の問題を、効率の名のもとに釈明しようとしている。 だがしかし、 R6RS の構文レイヤが主張する効率性はまやかしに過ぎないのだった。 実際、 R6RS の設計には、(この SRFI で規定する)構文レイヤでも手続きレイヤでもレコード型を同一のものとして扱うという、より単純で直交性のある設計より優れたところはない。
以上で述べた問題は R6RS の投票に入る前に知られてい、文書化もされていたが、ともかく R6RS は承認されてしまった[22]。 こうなった今できる最良のことは、 SRFI でよりよいレコード機構を規定し、 R6RS ライブラリドキュメントのレコード機構を使った際に遭遇するであろう問題をプログラマに警告しておくことである。
本 SRFI で述べる ERR5RS 構文レイヤは Rees/Curtis/Duba/Flatt/Krishnamurthi/Kelsey/SRFI-9 流れを汲み、 ERR5RS や R6RS の手続きレイヤとの相互運用性を高めるために若干の改良をほどこしたものである。
本 SRFI の規定する define-record-type
は SRFI 9 のものと互換であり、それに拡張をほどこしたものである。
SRFI 9 はもっとも広く受け容れられた SRFI のひとつである。
拡張したのは、単一継承にかかる部分、(選択制の)暗黙の命名、それから、フィールドの変更可能性を指定するための簡略記法である。
本 SRFI で規定する手続きレイヤは define-record-type
構文と完全互換である。
手続きレイヤと構文レイヤのどちらからでも、
親レコード型がどちらのレイヤで定義されたかを知ることなしに、
定義済みレコード型を継承して新たなレコードを定義することができる。
R6RS の実装系では、 SRFI 99 のレコード型は R6RS のレコード型記述子に対応する。 可搬性のあるライブラリから SRFI 99 の構文レイヤで定義したレコード型を安全に公開することができる。
手続き名に関して言えば、 SRFI 99 では rtd
をレコード型記述子の略称として使っている。
こうすることにより、 SRFI 99 と R6RS の手続きレイヤや調査レイヤでの名前の衝突を防ぎ、
R6RS プログラムに SRFI 99 ライブラリを import
するのがより簡単になるようにしている。
ただし、 R6RS プログラムに R6RS 構文レイヤを import
する際には注意が必要である。
というのは、 R6RS の構文レイヤライブラリの公開している識別子は SRFI 9 とも
SRFI 99 とも競合しているからである。
適切に実装すれば SRFI 99 のレコードの効率は R6RS レコードとまったく同等である。
SRFI 99 は、仕様においても実装においても R6RS より単純である。
SRFI 99 は、閉じたレコードや不透明なレコード、非生成的なレコードを提供することを求めていないので、
その表現力は R6RS レコードより真に小さい。
他方で、 SRFI 99 では、拡張としてこれらみっつ(make-rtd
の sealed
、 opaque
、 uid
引数)についても触れている。これらを実装すれば、 SRFI 99 の表現力は
R6RS レコードと同等になるだろう。
この拡張により、 SRFI 99 は R6RS レコードを実装するための単純で効率的な基盤となるであろう。
本 SRFI で述べるレコードシステムは Larceny で実装されてきた。
これは Larceny で
((rnrs records syntactic)
ライブラリを含む)
R6RS を実装するために使われた基盤のレコードシステムである。
Larceny は ERR5RS レコードの効率性と、 SRFI 9 と ERR5RS、 R6RS の手続きレイヤ・調査レイヤの相互運用性の容易さを実証するものである。
make-record-constructor-descriptor
と互換である。例えば、 Larceny では手続きを ERR5RS
レコード記述子とともに使うことを認めている)
define-record-type
構文では変更可能なフィールドに対しても変更不可能なフィールドに対しても簡潔な記法を容易する。
フィールド指定子として識別子のみを指定したものは変更不可能であり、単一の識別子を括弧で包んだものは変更可能である。
make-rtd
手続きはフィールド指定子のリストではなくフィールド指定子のベクタを受け取る
(これは R6RS の手続きレイヤの対応する手続きからの持ち越しである)
SRFI 99 の実装系はすべて以下のライブラリを提供しなければならない。
(srfi :99) ; (srfi :99 records) の別名 (srfi :99 records) ; 以下みっつの複合ライブラリ (srfi :99 records procedural) (srfi :99 records inspection) (srfi :99 records syntactic)
ERR5RS の実装系は以下の別名についても提供すべきである。
(err5rs records) ; (srfi :99 records) の別名 (err5rs records procedural) ; (srfi :99 records procedural) の別名 (err5rs records inspection) ; (srfi :99 records inspection) の別名 (err5rs records syntactic) ; (srfi :99 records syntactic) の別名
本仕様では Scheme の標準の等価性述語がレコードに対してどのように振る舞うかについても説明する。 また、 R6RS 中のプログラム例が ERR5RS ライブラリを使うとどのようになるのかも示す。
以下で、ある手続きが R6RS の何らかの手続きの同等であると言うのは、引数がすべて R6RS の仕様で定められた性質を持つ場合だけである。 ERR5RS も本 SRFI も、仕様に反するプログラムが R6RS の例外の意味論に従うことを特に求めない。
(srfi :99 records procedural)
ライブラリでは以下の手続きを公開する。
(make-rtd name fieldspecs)
(make-rtd name fieldspecs parent)
name はシンボルで、調査レイヤの
rtd-name
手続きで使われるだけのものである。
fieldspecs はフィールド指定子のベクタであり、各フィールド指定子か以下のいずれかである。
(mutable name)
の形式のリストで、
name は変更可能なフィールドの名前を表すシンボル(immutable name)
の形式のリストで、
name は変更不可能なフィールドの名前を表すシンボル
parent は省略可能で、指定する場合には rtd か #f
である。
fieldspecs 中のシンボルのいずれかが
fieldspecs 中の複数のフィールドの名前となる場合はエラーである。
ただし、 fieldspecs 中のフィールド名は parent
の表すレコード型のフィールド名を隠蔽してもかまわない。
実装系でこの手続きを拡張して R6RS の非生成的なレコードや閉じたレコード、不透明なレコードをサポートしたいと考えるかもしれない。 推奨する方法は parent 引数の後に以下のような引数の任意の組み合わせを指定できるようにすることである。
sealed
は戻り値の rtd が他の rtd の親として利用できないことを意味する
opaque
は record?
手続きが戻り値の rtd のインスタンスを識別しないことを意味する。
uid
に他のシンボル id を続けたものは、
戻り値の rtd が uid id に関して非生成的であることを意味する。
この拡張の意味は R6RS で述べられているものと同じである。
以上の方法は SRFI 99 の実装系に求められることではない。 閉じた rtd や不透明な rtd、非生成的な rtd を実現する方法は他にもあるであろう。
R6RS 手続きレイヤを使い R6RS 互換なレコード型記述子を返すには以下のように定義すればよい (エラー処理や、上で述べた拡張は省いた)。
(define (make-rtd name fieldspecs . rest) (make-record-type-descriptor name (if (null? rest) #f (car rest)) #f #f #f (vector-map (lambda (fieldspec) (if (symbol? fieldspec) (list 'mutable fieldspec) fieldspec)) fieldspecs)))
(rtd? obj)
R6RS の record-type-descriptor?
と同等である。
(rtd-constructor rtd)
(rtd-constructor rtd fieldspecs)
rtd はレコード型記述子であり、 fieldspecs は省略可能なシンボルのベクタである。
fieldspecs 引数が与えられなかった場合、
rtd-constructor
は手続きを返し、その手続きは rtd
の表すレコード型のフィールドそれぞれに対応する引数を取り、そのレコード型のインスタンスを受け取った引数で初期化したものを返す。
このとき、親レコード型のフィールドに対応する引数が(もしあれば)先になる。
fieldspecs が与えられた場合も
rtd-constructor
は手続きを返し、その手続きは fieldspecs
の各要素に対応する引数を取り、
rtd の表すレコード型のインスタンスを生成し、
fieldspecs で指定した名前のフィールドを対応する引数で初期化したものを返す。
fieldspecs に同名のシンボルが二回以上現れた場合はエラーである。 子レコード型は親レコード型の同名のフィールドを隠蔽する。 fieldspecs 引数を隠蔽されたフィールドを初期化する目的に使うことはできない。
注: 省略可能な第二引数は Pavel Curtis の提案によるものである。 これにより、 SRFI 9 とうまく相互運用できるようになった。
この手続きは R6RS の手続きレイヤと ERR5RS の調査レイヤを使うと次のように定義することができる。
(define (rtd-constructor rtd . rest) ; 構築子呼び出し時ではなく、構築子生成時に並べ替え用バッファを計算する。 ; より細かなエラーチェックをすることを推奨する。 (define (make-constructor fieldspecs allnames maker) (let* ((k (length fieldspecs)) (n (length allnames)) (buffer (make-vector n (unspecified))) (reverse-all-names (reverse allnames))) (define (position fieldname) (let ((names (memq fieldname reverse-all-names))) (assert names) (- (length names) 1))) (let ((indexes (map position fieldspecs))) ; この部分は Larceny の mal のような ; 何らかの低レベルの言語で手書きすればかなり効率化することができる。 ; 多くのシステムでは case-lambda にするだけでも十分だろう。 (lambda args (assert (= (length args) k)) (for-each (lambda (arg posn) (vector-set! buffer posn arg)) args indexes) (apply maker (vector->list buffer)))))) (if (null? rest) (record-constructor (make-record-constructor-descriptor rtd #f #f)) (begin (assert (null? (cdr rest))) (make-constructor (vector->list (car rest)) (vector->list (rtd-all-field-names rtd)) (record-constructor (make-record-constructor-descriptor rtd #f #f))))))
(rtd-predicate rtd)
R6RS の record-predicate
手続きと同等である。
(rtd-accessor rtd field)
field はレコード記述子 rtd の表すレコード型のフィールドの名前である。 rtd (または rtd を継承したレコード型)のインスタンスを受け取り、指定されたフィールドの現在の値を返す 1 引数手続きを返す。
子レコード型は親レコード型の同名のフィールドを隠蔽する。
(rtd-mutator rtd field)
field はレコード記述子 rtd の表すレコード型のフィールドの名前である。 rtd (または rtd を継承したレコード型)のインスタンスと指定されたフィールドに新たに設定する値を受け取る 2 引数手続きを返す。 設定手続きは副作用を起こし、その戻り値は規定されていない。
子レコード型は親レコード型の同名のフィールドを隠蔽する。
(srfi :99 records inspection)
ライブラリは以下の手続きを公開する。
(record? obj)
R6RS の同名の手続きと同等である。
(record-rtd record)
R6RS の同名の手続きと同等である。
(rtd-name rtd)
R6RS の record-type-name
と同等である。
(rtd-parent rtd)
R6RS の record-type-parent
と同等である。
(rtd-field-names rtd)
R6RS の record-type-field-names
と同等である
(rtd の表すレコード型のフィールド名から、親レコード型のフィールド名を除いたものをシンボルのベクタとして返す)。
(rtd-all-field-names rtd)
rtd の表すレコード型のフィールド名を、(もしあれば)親レコード型のフィールド名も含めて、シンボルのベクタとして返す。
親レコード型のフィールド名は子レコード型のものより先に来、各部分列の名前の順は
rtd とその先祖レコード型のレコード型記述子に
rtd-field-names
を適用した戻り値のベクタと同じ順番である。
この手続きは次のように定義することができる。
(define (rtd-all-field-names rtd) (define (loop rtd othernames) (let ((parent (rtd-parent rtd)) (names (append (vector->list (rtd-field-names rtd)) othernames))) (if parent (loop parent names) (list->vector names)))) (loop rtd '()))
(rtd-field-mutable? rtd field)
rtd はレコード型記述子であり、 field
は rtd の表すレコード型のフィールド名を表すシンボルである。
指定したフィールドが変更可能である場合には #t
を返し、さもなければ
#f
を返す。
構文レイヤは SRFI 9 を拡張し、単一継承と(選択制の)暗黙の命名を加えたものである。
ERR5RS のレコード型定義は生成的である。 ただし、 SRFI 9 のトップレベルにしか現れることができないという制限は取り払った。 これは主に、 R6RS では生成的な定義は定義の現れるところではどこでも現れてよいとしたからである。
(srfi :99 records syntactic)
ライブラリは
以下で規定する
define-record-type
構文を公開する。
ERR5RS レコード型定義の構文は以下の通りである。
<definition> -> <record type definition> ; R5RS 7.1.6 節に追加 <record type definition> -> (define-record-type <type spec> <constructor spec> <predicate spec> <field spec> ...) <type spec> -> <type name> -> (<type name> <parent>) <constructor spec> -> #f -> #t -> <constructor name> -> (<constructor name> <field name> ...) <predicate spec> -> #f -> #t -> <predicate name> <field spec> -> <field name> -> (<field name>) -> (<field name> <accessor name>) -> (<field name> <accessor name> <mutator name>) <parent> -> <expression> <type name> -> <identifier> <constructor name> -> <identifier> <predicate name> -> <identifier> <accessor name> -> <identifier> <mutator name> -> <identifier> <field name> -> <identifier>
レコード型定義の意味は SRFI 9 と同じである。 レコード型定義は以下のような定義の並びにマクロ展開される
<type name>
を新しいレコード型のレコード型記述子に束縛する定義
<constructor spec>
が #f
でない場合)
新しいレコード型インスタンスの構築子の定義
<predicate spec>
が #f
でない場合)
新しいレコード型とその下位型のインスタンスを識別する述語の定義
ERR5RS のレコード型定義では SRFI 9 を拡張して以下のような選択肢を追加した。
<parent>
を指定した場合、その式は定義しようとしているレコード型の親レコード型の
rtd に評価されなければならない。
<constructor spec>
や <predicate spec>
に #f
を指定した場合、構築子や述語手続きは定義されない
(これは、定義したレコード型を抽象基底クラスとする場合に便利である)。
<constructor spec>
や <predicate spec>
に #t
を指定した場合、構築子の名前は
<type name>
に make-
を前置したものになり、
述語の名前は
<type name>
に疑問符(?
)を後置したものになる。
<constructor spec>
に #t
か単一の識別子を指定した場合、構築子の引数は、(もしあれば)親レコード型のフィールドに定義中のレコード型のフィールドの続いたものになる。
<field spec>
が単一の識別子の場合、
<type name>
にハイフン(-
)、
<field name>
の続いたものになる。<field spec>
が単一の識別子を要素とするリストの場合、
<type name>
にハイフン(-
)、
<field name>
の続いたものになる。
<type name>
にハイフン(-
)、 <field name>
、
-set!
の続いたものになる。
ふたつの ERR5RS レコード eqv?
であるのは、それぞれが同一の(動的な)レコード構築子の呼び出しで作成された場合、かつその場合のみである。
ふたつの ERR5RS レコード eq?
であるのは、それらが eqv?
である場合、かつその場合のみである。
record?
述語が真を返すようなふたつ ERR5RS レコードが
equal?
であるのは、それらが eqv?
である場合、かつその場合のみである。
本 SRFI では不透明なレコードに対する equal?
の意味を規定しない
(R6RS では、引数のレコードが偶然ペアやベクタ、文字列、バイトベクタを表現する不透明なレコードであったりしなければ、
equal?
と eqv?
はすべてのレコードに同様に振る舞うことを求めている)。
(歴史的な注: Pavel Curtis は equal?
は
eqv?
と同様に振る舞うべきだと提案した)
define-record-type
は make-rtd
を呼び出すコードにマクロ展開され、展開後の定義が実行されるたびに
make-rtd
が呼び出される。
ふたつの ERR5RS レコード型記述子が eqv?
であるのは、
同一の(動的な) make-rtd
の呼び出しで生成された場合、かつその場合のみである。
R6RS ライブラリの 6.3 節には、 R6RS と ERR5RS のレコードシステムを比較するのに恰好の例がある。 これは特に、このふたつの例が R6RS のレコード構築子記述子と継承を組み合わせた場合の使い方を強調するために考えられたものだからである。
ERR5RS レコードを使うと最初の例は以下のようになる。
(define rtd1 (make-rtd 'rtd1 '#((immutable x1) (immutable x2)))) (define rtd2 (make-rtd 'rtd2 '#((immutable x3) (immutable x4)) rtd1)) (define rtd3 (make-rtd 'rtd3 '#((immutable x5) (immutable x6)) rtd2)) (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 make-rtd1 (protocol1 (rtd-constructor rtd1))) (define make-rtd2 (let ((maker2 (rtd-constructor rtd2))) (protocol2 (protocol1 (lambda (x1 x2) (lambda (x3 x4) (maker2 x1 x2 x3 x4))))))) (define make-rtd3 (let ((maker3 (rtd-constructor rtd3))) (protocol3 (protocol2 (protocol1 (lambda (x1 x2) (lambda (x3 x4) (lambda (x5 x6) (maker3 x1 x2 x3 x4 x5 x6))))))))) (make-rtd3 1 2 3 4 5 6 7 8 9) ; フィールドの値が ; 3 5 9 11 15 17 ; であるレコードに評価される
R6RS のレコード構築子記述子の目的は、上の make-rtd1
や
make-rtd2
、 make-rtd3
の定義にあるようなイディオムを自動化し、また、下の
make-point/abs
や make-cpoint/abs
に見られるようなコードの重複を避けるときに、手続きによる抽象化以外の手段を提供することであった。
第二の例は親レコード型のフィールドが派生したレコード型で隠蔽されることを説明するものである。 ERR5RS レコードを使うとこの例は以下のようになる。
(define :point (make-rtd 'point '#((mutable x) (mutable y)))) (define make-point (rtd-constructor :point)) (define point? (rtd-predicate :point)) (define point-x (rtd-accessor :point 'x)) (define point-y (rtd-accessor :point 'y)) (define point-x-set! (rtd-mutator :point 'x)) (define point-y-set! (rtd-mutator :point 'y)) (define p1 (make-point 1 2)) (point? p1) => #t (point-x p1) => 1 (point-y p1) => 2 (point-x-set! p1 5) (point-x p1) => 5 (define :point2 (make-rtd 'point2 '#((mutable x) (mutable y)) :point)) (define make-point2 (rtd-constructor :point2)) (define point2? (rtd-predicate :point2)) (define point2-xx (rtd-accessor :point2 'x)) (define point2-yy (rtd-accessor :point2 'y)) (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 make-point/abs (let ((maker (rtd-constructor :point))) (lambda (x y) (maker (abs x) (abs y))))) (point-x (make-point/abs -1 -2)) => 1 (point-y (make-point/abs -1 -2)) => 2 (define :cpoint (make-rtd 'cpoint '#((mutable rgb)) :point)) (define make-cpoint (let ((maker (rtd-constructor :cpoint))) (lambda (x y c) (maker x y (color->rgb c))))) (define make-cpoint/abs (let ((maker (rtd-constructor :cpoint))) (lambda (x y c) (maker (abs x) (abs y) (color->rgb c))))) (define cpoint-rgb (rtd-accessor :cpoint 'rgb)) (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
保守の簡単のために参照実装は別に提供するものとする。
I am grateful to David Rush and Andre van Tonder for their comments and criticisms, to all those mentioned by name in the Rationale, and to all who participated in the archived discussion of this SRFI. The reference implementation is adapted from Larceny v0.97.
Copyright (C) William D Clinger 2008. 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. REMEMBER, THERE IS NO SCHEME UNDERGROUND. 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.