-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsrfi-76.html
220 lines (190 loc) · 48.7 KB
/
srfi-76.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>SRFI 76: R6RS Records</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/srfi.css" type="text/css" />
</head>
<body>
<H1>Title</H1>
R6RS Records
<H1>Authors</H1>
Will Clinger, R. Kent Dybvig, Michael Sperber, Anton van Straaten
<H1>Status</H1>
<p>This SRFI is currently in <em>withdrawn</em> status. Here is <a href="https://srfi.schemers.org/srfi-process.html">an explanation</a> of each status that a SRFI can hold. To provide input on this SRFI, please send email to <code><a href="mailto:srfi+minus+76+at+srfi+dotschemers+dot+org">srfi-76@<span class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the list, follow <a href="https://srfi.schemers.org/srfi-list-subscribe.html">these instructions</a>. You can access previous messages via the mailing list <a href="https://srfi-email.schemers.org/srfi-76">archive</a>.</p>
<UL>
<LI>Received: 2005-09-01
<LI>Draft: 2005-09-12 - 2006-04-01
<LI>Revised: 2005-10-17
<LI>Revised: 2005-12-31
<LI>Withdrawn: 2006-04-24
</UL>
<h1>Note</h1><p>This SRFI is being submitted by members of the Scheme Language Editor's Committee as part of the Scheme standardization process. The purpose of such ``R6RS SRFIs'' is to inform the Scheme community of features and design ideas under consideration by the editors and to allow the community to give the editors some direct feedback that will be considered during the design process.</p><p>At the end of the discussion period, this SRFI will be withdrawn. When the R6RS specification is finalized, the SRFI may be revised to conform to the R6RS specification and then resubmitted with the intent to finalize it. This procedure aims to avoid the situation where this SRFI is inconsistent with R6RS. An inconsistency between R6RS and this SRFI could confuse some users. Moreover it could pose implementation problems for R6RS compliant Scheme systems that aim to support this SRFI. Note that departures from the SRFI specification by the Scheme Language Editor's Committee may occur due to other design constraints, such as design consistency with other features that are not under discussion as SRFIs. </p><h1>Abstract</h1><p>This SRFI describes abstractions for creating new data types representing records - data structures with named fields. This SRFI comes in four parts:</p><ul><li>a procedural layer for creating and manipulating record types and record
instances</li><li>an explicit-naming syntactic layer for defining the various entities associated with a record type - construction procedure, predicate, field accessors, mutators, etc. - at once</li><li>an implicit-naming syntactic layer built on top of the explicit-naming syntactic layer, which chooses the names for the various products based on the names of the record type and fields</li><li>a set of reflection procedures</li></ul><h1>Rationale</h1><p>The procedural layer allows dynamic construction of new record types and associated procedures for creating and manipulating records, which is particularly useful when writing interpreters that construct host-compatible record types. It may also serve as a target for expansion of the syntactic layers.</p><p>The procedural layer allows record types to be extended. This allows using record types to naturally model hierarchies that occur in applications like algebraic data types, and also single-inheritance class systems. This model of extension has a well-understood representation that is simple to implement.</p><p>The explicit-naming syntactic layer provides a basic syntactic interface whereby a single record definition serves as a shorthand for the definition of several record creation and manipulation routines: a construction procedure, a predicate, field accessors, and field mutators. As the name suggests, the explicit-naming syntactic layer requires the programmer to name each of these products explicitly. The explicit-naming syntactic layer is similar to <a href="https://srfi.schemers.org/srfi-9/">SRFI 9: Defining Record Types</a>, but adds several features, including single inheritance and non-generative record types.</p><p>The implicit-naming syntactic layer extends the explicit-naming syntactic layer by allowing the names for the construction procedure, predicate, accessors, and mutators to be determined automatically from the name of the record and names of the fields. This establishes a standard naming convention and allows record-type definitions to be more succinct, with the downside that the product definitions cannot easily be located via a simple search for the product name. The programmer may override some or all of the default names by specifying them explicitly as in the explicit-naming syntactic layer.</p><p>The syntax of both syntactic layers is also designed to allow future extensions by using clauses introduced by keywords.</p><p>The two layers are designed to be fully compatible; the implicit-naming layer is simply a conservative extension of the explicit-naming layer. The goal is to make both explicit-naming and implicit-naming definitions reasonably natural while allowing a seamless transition between explicit and implicit naming.</p><p>The reflection procedures allow programs to obtain from a record instance a descriptor for the type and from there obtain access to the fields of the record instance. This allows the creation of portable printers and inspectors. A program may prevent access to a record's type and thereby protect the information stored in the record from the reflection mechanism by declaring the type <i>opaque</i>. Thus, opacity as presented here can be used to enforce abstraction barriers.</p><p>Fresh record types may be generated at different times---for example, when a record-type-defining form is expanded, or when it is evaluated. The available choices all have different advantages and trade-offs. These typically come into play when a record-type-defining form may be evaluated multiple times, for example, as part of interactive operation of the Scheme system, or as a body form of an abstraction. The SRFI leaves the default generativity largely unspecified to allow different implementations, but also provides for non-generativity, which guarantees that the evaluation of identical record-type-defining forms yields compatible record types.</p><h1>Issues</h1><ul><li><p>The name <code>define-record-type</code> is used for both the implicit-naming and explicit-naming syntactic interfaces. It is unclear whether both names should in fact be the same. With different names it would be easier to identify when only the explicit-naming interface is being used; presumably, a module system would also make this possible. On the other hand, with different names it would also be more difficult to transition between the two interfaces, and the name to use for partly implicit, partly explicit record definitions might not be obvious.</p></li><li><p>Compared to some other record-defining forms that have been proposed and implemented, the syntax is comparatively verbose. For instance, PLT Scheme has a <code>define-struct</code> form that allows record-type definitions as short as this:</p><pre>(define-struct point (x y))
</pre><p>The <a href="#design-rationale">Design Rationale section</a> explains why.</p><p>It would be possible to introduce abbreviations into the syntax. In the <code>fields</code> clause, a single identifier might serve as a shorthand for a <code>mutable</code> field clause:</p><pre>(define-record-type point (fields x y))
</pre><p>Allowing such abbreviations would make some record definitions more concise but may also discourage programmers from specifying valuable field mutability information. In any case, it is trivial to define forms like <code>define-struct</code> on top of this proposal.</p></li><li><p>Similarly, one might allow plain symbols to be used as field specifiers in the <var>fields</var> argument to <code>make-record-type-descriptor</code>, defaulting to mutability or immutability.</p></li><li><p>Macros that expand into the implicit-naming layer might have unexpected behavior, as field names that are distinct as identifiers may not be distinct as symbols, which is how they're used. For this reason, and to simplify the proposal, should the implicit-naming layer be dropped?</p></li><li><p>The specification of <code>make-record-type-descriptor</code> has this:</p><blockquote>If <var>parent</var> is not <code>#f</code>, and <var>uid</var> is not <code>#f</code>, and the parent is generative (i.e. its uid is <code>#f</code>), an error is signalled.</blockquote><p>However, the semantics would be perfectly clear even in the error case described above. Should this restriction be lifted, if only for reasons of simplicity?</p></li><li><p>The specification of <code>eq?</code> on records allows certain kinds of unboxing optimizations, at the cost of leaving its behavior on records unspecified. Should instead the following be required to hold for immutable records as well?</p><pre>(let ((r (construct ...)))
(eq? r r)) ==> #t
</pre></li><li><p>The behavior of <code>equal?</code> on records is one of several possibilities. See the <a href="http://www.lispworks.com/documentation/HyperSpec/Issues/iss143.htm">Issue EQUAL-STRUCTURE Writeup</a> in the Common Lisp HyperSpec on why any behavior of <code>equal?</code> on records is wrong for some purposes.</p></li><li><p>There is no way to use a record-type descriptor created by an explicit call to <code>make-record-type-descriptor</code> as a parent type in a <code>define-record-type</code> form. Should this be rectified, for example by another <code>define-record-type</code> clause named <code>parent-rtd</code>?</p></li><li><p>The time at which a record-type descriptor is generated for the syntactic record-type-definition forms is presently unspecified. Should this be tightened, and, if so, to what kind of generativity?</p></li><li><p>Record types defined via the syntactic layer default to non-opaque. Should they default to opaque instead?</p></li><li><p>The concepts of typed aggregates (with subtyping) with positional addressing and opacity can be separated from the much heavier and more arbitrary composite notion of records with named fields defined in this SRFI - see the reference implementation on how it's done. Should these be the primitive part of the standard, and records derived from them?</p></li><li><p>Functional update and/or copy operations would be useful additions. (See <a href="https://srfi.schemers.org/srfi-76/mail-archive/msg00066.html">this post</a> for some discussion on the issue.) However, there are several design issues with these operations:</p><ul><li>Should a copy/update operation for a given record type be able to copy records of an extension?</li><li>If so, what should the semantics be?</li><li>If so, should a record type be able to prevent copy/update for its children?</li><li>How does update interact with the regular creation of new records? Specifically, can updaters be built using something similar to the descriptors used for creating constructors?</li></ul></li><li><p>Should the <code>nongenerative</code> clause take an expression operand rather than a symbol, as argued <a href="https://srfi.schemers.org/srfi-76/mail-archive/msg00056.html">here</a>?</p></li><li><p>The <code>sealed</code> clause presently lacks a clear rationale, and may prohibit desirable extensions to an existing program where source code is unavailable or cannot be modified. Should it be flushed? Discussion on the topic can be found <a href="https://srfi.schemers.org/srfi-76/mail-archive/msg00103.html">here</a>.</p></li></ul><h1>Specification</h1><h2>Procedural layer</h2><dl><dt><code>(make-record-type-descriptor </code><var>name</var> <var>parent</var> <var>uid</var> <var>sealed?</var> <var>opaque?</var> <var>fields</var><code>)</code></dt><dd><p>This returns a <i>record-type descriptor</i>, or <i>rtd</i>. The rtd represents a record type distinct from all built in types and other record types. The rtd and the data type it represents are new except possibly if <var>uid</var> is provided (see below).</p><p>The <var>name</var> argument must be a symbol naming the record type; it is purely for informational purposes, and may be used for printing by the underlying Scheme system.</p><p>The <var>parent</var> argument is either <code>#f</code> or an rtd. If it is an rtd, the returned record type, <i>t</i>, <i>extends</i> the record type <i>p</i> represented by <var>parent</var>. Each record of type <i>t</i> is also a record of type <i>p</i>, and all operations applicable to a record of type <i>p</i> are also applicable to a record of type <i>t</i>, except for reflection operations if <i>t</i> is opaque but <i>p</i> is not. An error is signalled if <var>parent</var> is sealed (see below).</p><p>The extension relationship is transitive in the sense that a type extends its parent's parent, if any, and so on. </p><p>The <var>uid</var> argument is either <code>#f</code> or a symbol. If it is a symbol, the created record type is <i>non-generative</i>, i.e. there may be only one record type with that uid in the entire system (in the sense of <code>eqv?</code>). When <code>make-record-type-descriptor</code> is called repeatedly with the same <var>uid</var> argument (in the sense of <code>eq?</code>), the <var>parent</var> argument must be the same in the sense of <code>eqv?</code> (more on this below), and the <var>uid</var>, <var>sealed?</var>, <var>opaque?</var>, and <var>fields</var> arguments must be the same in the sense of <code>equal?</code>. In this case, the same record-type descriptor (in the sense of <code>eqv?</code>) is returned every time. If a call with the same uid differs in any argument, an error is signalled. If <var>uid</var> is <code>#f</code>, or if no record type with the given uid has been created before, <code>make-record-type-descriptor</code> returns a fresh record-type descriptor representing a new type disjoint from all other types.</p><blockquote><i>Note: </i>Users are strongly strongly encouraged to use symbol names constructed using the <a href="http://www.ietf.org/rfc/rfc4122">UUID namespace</a> (for example, using the record-type name as a prefix) for the <var>uid</var> argument.</blockquote><p>If <var>parent</var> is not <code>#f</code>, and <var>uid</var> is not <code>#f</code>, and the parent is generative (i.e. its uid is <code>#f</code>), an error is signalled. In other words, the parent of a non-generative rtd must be non-generative itself.</p><p>The <var>sealed?</var> flag is a boolean. If true, the returned record type is <i>sealed</i>, i.e., it cannot be extended.</p><p>The <var>opaque?</var> flag is a boolean. If true, the returned record type is <i>opaque</i>. This means that calls to <code>record?</code> will return <code>#f</code> and <code>record-rtd</code> (see "Reflection" below) will signal an error. The record type is also opaque if an opaque parent is supplied. If <var>opaque?</var> is false and an opaque parent is not supplied, the record is not opaque.</p><p>The <var>fields</var> argument must be a list of <var>field specifiers</var>. Each <var>field specifier</var> must be a list of the form <code>(mutable </code><var>name</var><code>)</code>, or a list of the form <code>(immutable </code><var>name</var><code>)</code>. The specified fields are added to the parent fields, if any, to determine the complete set of fields of the returned record type. Each <var>name</var> must be a symbol and names the corresponding field of the record type; the names need not be distinct. A field with tag <code>mutable</code> may be modified, whereas an attempt to obtain a mutator for a field with tag <code>immutable</code> will signal an error.</p><p>Where field order is relevant, e.g., for record construction and field access, the fields are considered to be ordered as specified, although no particular order is required for the actual representation of a record instance.</p><p>A record type whose complete set of fields are all immutable is considered <i>immutable</i> itself. Conversely, a record type is considered <i>mutable</i> if there is at least one mutable field in its complete set of fields.</p><p>A generative record-type descriptor created by a call to <code>make-record-type-descriptor</code> is not <code>eqv?</code> to any record-type descriptor (generative or non-generative) created by another call to <code>make-record-type-descriptor</code>. A generative record-type descriptor is <code>eqv?</code> only to itself, i.e., <code>(eqv? rtd1 rtd2)</code> iff <code>(eq? rtd1 rtd2)</code>. Moreover:</p><pre>(let ((rtd (make-record-type-descriptor ...)))
(eqv? rtd rtd)) ==> #t
</pre><p>Note that this does <em>not</em> imply the following:</p><pre>(let ((rtd (make-record-type-descriptor ...)))
(eq? rtd rtd)) ==> #t
</pre><p>Also, two non-generative record-type descriptors are <code>eqv?</code> iff they were successfully created by calls to <code>make-record-type-descriptor</code> with the same <var>uid</var> arguments.</p></dd><dt><code>(record-type-descriptor? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is a record-type descriptor, <code>#f</code> otherwise.</p></dd><dt><code>(make-record-constructor-descriptor </code><var>rtd</var> <var>parent-constructor-descriptor</var> <var>protocol</var><code>)</code></dt><dd><p>This returns a <i>record-constructor descriptor</i> (or <i>constructor descriptor</i> for short) that can be used to create record constructors (via <code>record-constructor</code>; see below) or other constructor descriptors. <var>Rtd</var> must be a record-type descriptor. <var>Protocol</var> is a <i>protocol</i>, that describes how to initialize the fields of<var>rtd</var> in the record when it is constructed. The protocol is a procedure of one parameter that must itself return a procedure, the <i>constructor</i>. The <var>protocol</var> procedure is called by <code>record-constructor</code> with a procedure as an argument that can be used to construct the record object itself and seed the fields of the parent types of <var>rtd</var> with initial values.</p><p>If <var>rtd</var> is <em>not</em> an extension of another record type, then <var>parent-constructor-descriptor</var> must be <code>#f</code>. In this case, the protocol receives as argument a procedure <var>new</var> that has a parameter for every field of <var>rtd</var>; <var>new</var> will return a record object with the fields of <var>rtd</var> initialized to its arguments.</p><p>Protocol example:</p><pre>(lambda (new) (lambda (v ...) (new v ...)))
</pre><p>Here, the call to <code>new</code> will return a record where the fields of <var>rtd</var> are simply initialized with the arguments <code>v ...</code>.</p><p>As the protocol can be used to construct records of an extension of <var>rtd</var>, the record returned by <var>new</var> may actually be of a record type extending <var>rtd</var>. (See below.)</p><p>If <var>rtd</var> <em>is</em> an extension of another record type <var>rtd'</var>, <var>parent-constructor-descriptor</var> itself must be a constructor descriptor of <var>rtd'</var> (except for default values; see below). In this case, the protocol receives as argument a procedure <var>p</var> whose arguments will be passed unchanged to the constructor of <var>parent-constructor-descriptor</var>; <var>p</var> will return another procedure that accepts as argument the initial values for the fields of <var>rtd</var> and itself returns what the constructor of <var>parent-constructor-descriptor</var> returned, with the field values of <var>rtd'</var> (and its parent and so on) initialized according to <var>parent-constructor-descriptor</var> and with the field values of <var>rtd</var> initialized according to <var>p</var>.</p><p>As a matter of convention, the constructor created through the protocol should always return the record object itself.</p><p>Protocol example</p><pre>(lambda (p)
(lambda (x ... v ...)
(let ((construct (p x ...)))
(construct v ...))))
</pre><p>This will initialize the fields of the parent of <var>rtd</var> according to <var>parent-constructor-descriptor</var>, calling the associated constructor with <code>x ...</code> as arguments, and initializing the fields of <var>rtd</var> itself with <code>v ...</code>.</p><p>Summarizing: the constructor descriptors for a record type form a chain of protocols exactly parallel to the chain of record-type parents. Each constructor descriptor in the chain determines the field values for the associated record type.</p><p><var>Protocol</var> can be <code>#f</code>, specifying a default. This is only admissible if either <var>rtd</var> is not an extension of another record type, or, if it is, if <var>parent-constructor-descriptor</var> itself was constructed with a default protocol. In the first case, <var>protocol</var> will default to a procedure equivalent to the following:</p><pre>(lambda (p)
(lambda field-values
(apply p field-values)))
</pre><p>In the latter case, it will default to a protocol that returns a constructor that will accept as many arguments as <var>rtd</var> has total fields (i.e. as the sum of the number of fields in the entire chain of record types) and will return a record with fields initialized to those arguments, with the field values for the parent coming before those of the extension in the argument list.</p></dd><dt><code>(record-constructor </code><var>constructor-descriptor</var><code>)</code></dt><dd><p>Calls the protocol of record-constructor descriptor <var>constructor-descriptor</var> with an appropriate procedure <var>c</var> as an argument (see the description of <code>make-record-constructor-descriptor</code>) that will create a record of the record type associated with <var>constructor-descriptor</var>.</p><p>If the record type associated with <var>constructor-descriptor</var>is opaque, then the values created by such a constructor are not considered by the reflection procedures to be records; see the specification of <code>record?</code> below.</p><p>A record from an immutable record type is called <i>immutable</i>; conversely, a record from a mutable record type is called <i>mutable</i>.</p><p>Two records created by such a constructor are equal according to <code>equal?</code> iff they are <code>eqv?</code>, provided their record type was not used to implement any of the types explicitly mentioned in the definition of <code>equal?</code>.</p><p>If <code>construct</code> is bound to a constructor returned by <code>record-constructor</code>, the following holds:</p><pre>(let ((r (construct ...)))
(eqv? r r)) ==> #t
</pre><p>For mutable records, but not necessarily for immutable ones, the following holds:</p><pre>(let ((r (construct ...)))
(eq? r r)) ==> #t
</pre><p>For mutable records, the following holds:</p><pre>(let ((f (lambda () (construct ...))))
(eq? (f) (f))) => #f
</pre><p>For immutable records, the value of the above expression is unspecified.</p></dd><dt><code>(record-predicate </code><var>rtd</var><code>)</code></dt><dd><p>Returns a procedure that, given an object <var>obj</var>, returns <code>#t</code> iff <var>obj</var> is a record of the type represented by <var>rtd</var>.</p></dd><dt><code>(record-accessor </code><var>rtd</var> <var>k</var><code>)</code></dt><dd><p>Given a record-type descriptor <var>rtd</var> and an exact non-negative integer <var>k</var> that specifies one of the fields of <var>rtd</var>, <code>record-accessor</code> returns a one-argument procedure that, given a record of the type represented by <var>rtd</var>, returns the value of the selected field of that record.</p><p>It is an error if the accessor procedure is given something other than a record of the type represented by <var>rtd</var>. Note that the records of the type represented by <var>rtd</var> include records of extensions of the type represented by <var>rtd</var>.</p><p>The field selected is the one corresponding the the <var>k</var>th element (0-based) of the <var>fields</var> argument to the invocation of <code>make-record-type-descriptor</code> that created <var>rtd</var>. Note that <var>k</var> cannot be used to specify a field of any type <var>rtd</var> extends.</p></dd><dt><code>(record-mutator </code><var>rtd</var> <var>k</var><code>)</code></dt><dd><p>Given a record-type descriptor <var>rtd</var> and an exact non-negative integer <var>k</var> that specifies one of the mutable fields of <var>rtd</var>, <code>record-accessor</code> returns a two-argument procedure that, given a record <var>r</var> of the type represented by <var>rtd</var> and an object <var>obj</var>, stores <var>obj</var> within the field of <var>r</var> specified by <var>k</var>. The <var>k</var> argument is as in <code>record-accessor</code>. If <var>k</var> specifies an immutable field, an error is signalled.</p></dd></dl><h2>Explicit-Naming Syntactic Layer</h2><p>The record-type-defining form <code>define-record-type</code> is a
definition and can appear anywhere any other <definition> can appear.</p><dl><dt><code>(define-record-type </code><name-spec> <record clause> *<code>)</code> (syntax)</dt><dd><p>A <code>define-record-type</code> form defines a new record type along with associated constructor descriptor and constructor, predicate, field accessors and field mutators. The <code>define-record-type</code> form expands into a set of definitions in the environment where <code>define-record-type</code> appears; hence, it is possible to refer to the bindings (except for that of the record-type itself) recursively.</p><p>The <name-spec> specifies the names of the record type, construction procedure, and predicate. It must take the following form.</p><p><code>(</code><record name> <constructor name> <predicate name><code>)</code></p><p><Record name>, <constructor name>, and <predicate name> must all be identifiers.</p><p><Record name>, taken as a symbol becomes the name of the record type. Additionally, it is bound by this definition to an expand-time or run-time description of the record type for use as parent name in syntactic record-type definitions that extend this definition. It may also be used as a handle to gain access to the underlying record-type descriptor and constructor descriptor (see <code>record-type-descriptor</code> and <code>record-constructor-descriptor</code> below).</p><p><Constructor name> is defined by this definition to a constructor for the defined record type, with a protocol specified by the <code>protocol</code> clause, or, in its absence, using a default value. For details, see the description of the <code>protocol</code> clause below.</p><p><Predicate name> is defined by this definition to a predicate for the defined record type.</p><p>Each <record clause> must take one of the following forms; it is an error if multiple <record clause>s of the same kind appear in a <code>define-record-type</code> form.</p><dl><dt><code>(fields </code><field-spec> *<code>)</code></dt><dd><p>where each <field-spec> has one of the following forms</p><dl><dt><code>(</code><code>immutable </code><field name> <accessor name><code>)</code></dt><dt><code>(</code><code>mutable </code><field name> <accessor name> <mutator name><code>)</code></dt></dl><p><Field name>, <accessor name>, and <mutator name> must all be identifiers. The first form declares an immutable field called <field name>, with the corresponding accessor named <acccessor name>. The second form declares a mutable field called <field name>, with the corresponding accessor named <acccessor name>, and with the corresponding mutator named <mutator name>.</p><p>The <field name>s become, as symbols, the names of the fields of the record type being created, in the same order. They are not used in any other way.</p></dd><dt><code>(parent </code><parent name><code>)</code></dt><dd><p>This specifies that the record type is to have parent type <parent name>, where <parent name> is the <record name> of a record type previously defined using <code>define-record-type</code>. The absence of a <code>parent</code> clause implies a record type with no parent type.</p></dd><dt><code>(protocol </code><exp><code>)</code></dt><dd><p><Exp> is evaluated in the same environment as the <code>define-record-type</code> form, and must evaluate to a protocol appropriate for the record type being defined (see above in the description of <code>make-record-constructor-descriptor</code>). The protocol is used to create a record-constructor descriptor where, if the record-type being defined has a parent, the parent-type constructor descriptor is that associated with the parent type specified in the <code>parent</code> clause.</p><p>If no <code>protocol</code> clause is specified, a constructor descriptor is still created using a default protocol. The rules for this are the same as for <code>make-record-constructor-descriptor</code>: the clause can be absent only if the record type defined has no parent type, or if the parent definition does not specify a protocol.</p></dd><dt><code>(sealed </code><code>#t</code><code>)</code></dt><dt><code>(sealed </code><code>#f</code><code>)</code></dt><dd><p>If this option is specified, it means that the opacity of the type is the value specified as the operand. If no <code>sealed</code> option is present, the defined record type is not sealed.</p></dd><dt><code>(opaque </code><code>#t</code><code>)</code></dt><dt><code>(opaque </code><code>#f</code><code>)</code></dt><dd><p>If this option is specified, it means that the opacity of the type is the value specified as the operand. It is also opaque if an opaque parent is specified. If the <code>opaque</code> option is not present, the record type is not opaque.</p></dd><dt><code>(nongenerative </code><uid><code>)</code></dt><dd><p>This specifies that the record type be nongenerative with uid <uid>, which must be an <identifier>. The absence of a <code>nongenerative</code> clause implies that the defined type is generative. In the latter case, a new type may be generated once for each evaluation of the record definition or once for all evaluations of the record definition, but the type is guaranteed to be distinct even for verbatim copies of the same record definition appearing in different parts of a program.</p></dd></dl><p>Note that all bindings created by this form (for the record type, the construction procedure, the predicate, the accessors, and the mutators) must have names that are pairwise distinct.</p><p>For two non-generative record-type definitions with the same uid, if the implied arguments to <code>make-record-type-descriptor</code> would create an equivalent record-type descriptor, the created type is the same as the previous one. Otherwise, an error is signalled.</p><p>Note again that, in the absence of a <code>nongenerative</code> clause, the question of expand-time or run-time generativity is unspecified. Specifically, the return value of the following expression in unspecified:</p><pre>(let ((f (lambda (x) (define-record-type r ---) (if x r? (make-r ---)))))
((f #t) (f #f)))
</pre></dd><dt><code>(record-type-descriptor </code><record name><code>)</code> (syntax)</dt><dd><p>This evaluates to the record-type descriptor associated with the type specified by <record-name>.</p><p>Note that, in the absense, of a <code>nongenerative</code> clause, the return value of the following expression is unspecified:</p><pre>(let ((f (lambda () (define-record-type r ---) (record-type-descriptor r))))
(eqv? (f) (f)))
</pre><p>Note that <code>record-type-descriptor</code> works on both opaque and non-opaque record types.</p></dd><dt><code>(record-constructor-descriptor </code><record name><code>)</code> (syntax)</dt><dd><p>This evaluates to the record-constructor descriptor associated with <record-name>.</p></dd></dl><h2>Implicit-Naming Syntactic Layer</h2><p>The <code>define-record-type</code> form of the implicit-naming syntactic layer is a conservative extension of the <code>define-record-type</code> form of the explicit-naming layer: a <code>define-record-type</code> form that conforms to the syntax of the explicit-naming layer also conforms to the syntax of the implicit-naming layer, and any definition in the implicit-naming layer can be understood by its translation into the explicit-naming layer.</p><p>This means that a record type defined by the <code>define-record-type</code> form of either layer can be used by the other.</p><p>The implicit-naming syntactic layer extends the explicit-naming layer in two ways. First, <name-spec> may be a single identifier representing just the record name. In this case, the name of the construction procedure is generated by prefixing the record name with <code>make-</code>, and the predicate name is generated by adding a question mark (<code>?</code>) to the end of the record name. For example, if the record name is <code>frob</code> then the name of the construction procedure is <code>make-frob</code> and the predicate name is <code>frob?</code>.</p><p>Second, the syntax of <field-spec> is extended to allow the accessor and mutator names to be omitted. That is, <field-spec> can take one of the following forms as well as the forms described in the preceding section.</p><p>Note that the field names with implicitly-named accessors must be distinct to avoid a conflict between the accessors.</p><dl><dt><code>(</code><code>immutable </code><field name><code>)</code></dt><dt><code>(</code><code>mutable </code><field name><code>)</code></dt></dl><p>If <field-spec> takes one of these forms, then the accessor name is generated by appending the record name and field name with a hyphen separator, and the mutator name (for a mutable field) is generated by adding a <code>-set!</code> suffix to the accessor name. For example, if the record name is <code>frob</code> and the field name is <code>widget</code>, the accessor name is <code>frob-widget</code> and the mutator name is <code>frob-widget-set!</code>.</p><p>Any definition that takes advantage of implicit naming can be rewritten trivially to a definition that conforms to the syntax of the explicit-naming layer merely by specifing the names explicitly. For example, the implicit-naming layer record definition:</p><pre>(define-record-type frob
(fields (mutable widget))
(protocol
(lambda (c) (c (make-widget n)))))
</pre><p>is equivalent to the following explicit-naming layer record definition.</p><pre>(define-record-type (frob make-frob frob?)
(fields (mutable widget frob-widget frob-widget-set!))
(protocol
(lambda (c) (c (make-widget n)))))
</pre><p>With the explicit-naming layer, one can choose to specify just some of the names explicitly; for example, the following overrides the choice of accessor and mutator names for the <code>widget</code> field.</p><pre>(define-record-type (frob make-frob frob?)
(fields (mutable widget getwid setwid!))
(protocol
(lambda (c) (c (make-widget n)))))
</pre><h2>Reflection</h2><p>A set of procedures are provided for reflecting on records and their record-type descriptors. These procedures are designed to allow the writing of portable printers and inspectors. </p><p>Note that <code>record?</code> and <code>record-rtd</code> treat records of opaque record types as if they were not records. On the other hand, the reflection procedures that operate on record-type descriptors themselves are not affected by opacity. In other words, opacity controls whether a program can obtain an rtd from an instance. If the program has access to the original rtd via <code>make-record-type-descriptor</code> or <code>record-type-descriptor</code> it can reflect upon it.</p><dl><dt><code>(record? </code><var>obj</var><code>)</code></dt><dd><p>Returns <code>#t</code> if <var>obj</var> is a record, and its record type is not opaque. Returns <code>#f</code> otherwise.</p></dd><dt><code>(record-rtd </code><var>rec</var><code>)</code></dt><dd><p>Returns the rtd representing the type of <var>rec</var> if the type is not opaque. The rtd of the most precise type is returned; that is, the type <var>t</var> such that <var>rec</var> is of type <var>t</var> but not of any type that extends <var>t</var>. If the type is opaque, <code>record-rtd</code> signals an error.</p></dd><dt><code>(record-type-name </code><var>rtd</var><code>)</code></dt><dd><p>Returns the name of the record-type descriptor <var>rtd</var>.</p></dd><dt><code>(record-type-parent </code><var>rtd</var><code>)</code></dt><dd><p>Returns the parent of the record-type descriptor <var>rtd</var>, or <code>#f</code> if it has none.</p></dd><dt><code>(record-type-uid </code><var>rtd</var><code>)</code></dt><dd><p>Returns the uid of the record-type descriptor <var>rtd</var>, or <code>#f</code> if it has none. (An implementation may assign a generated uid to a record type even if the type is generative, so the return of a uid does not necessarily imply that the type is nongenerative.)</p></dd><dt><code>(record-type-generative? </code><var>rtd</var><code>)</code></dt><dd><p>Returns <code>#t</code> if <var>rtd</var> is generative, and <code>#f</code> if not.</p></dd><dt><code>(record-type-sealed? </code><var>rtd</var><code>)</code></dt><dd><p>Returns a boolean value indicating whether the record-type descriptor is sealed.</p></dd><dt><code>(record-type-opaque? </code><var>rtd</var><code>)</code></dt><dd><p>Returns a boolean value indicating whether the record-type descriptor is opaque.</p></dd><dt><code>(record-type-field-names </code><var>rtd</var><code>)</code></dt><dd><p>Returns a list of symbols naming the fields of the type represented by <var>rtd</var> (not including the fields of parent types) where the fields are ordered as described under <code>make-record-type-descriptor</code>.</p></dd><dt><code>(record-field-mutable? </code><var>rtd</var> <var>k</var><code>)</code></dt><dd><p>Returns a boolean value indicating whether the field specified by <var>k</var> of the type represented by <var>rtd</var> is mutable, where <var>k</var> is as in <code>record-accessor</code>.</p></dd></dl><h1><a name="design-rationale">Design Rationale</a></h1><h2>Protocols, constructor descriptors, and constructors</h2><p>The proposal contains infrastructure for creating specialized constructors, rather than just creating default constructors that just accept the initial values of all the fields as arguments. This infrastructure achives full generality while leaving each level of an inheritance hierarchy in control over its own fields and allowing child record definitions to be abstracted away from the actual number and contents of parent fields.</p><p>The design allows the initial values of the fields to be specially computed or default to constant values. It also allows for operations to be performed on or with the resulting record, such as the registration of a widget record for finalization. Moreover, the constructor-descriptor mechanism allows the creation of such initializers in a modular manner, separating the initialization concerns of the parent types of those of the extensions.</p><p>During the design phase as well as the discussion period of the SRFI, we experimented with several mechanisms for achieving this purpose; the one described here achieves complete generality without cluttering the syntactic layer, possibly sacrificing a bit of notational convenience in special cases, as compared to previous versions of this proposal.</p><h2>Non-distinct field names</h2><p>The field names provided as an argument to <code>make-record-type-descriptor</code> and in the syntactic layers are only for informational purposes for use in, say, debuggers. They aren't actively used anywhere else in the interface (though they were in an earlier draft) except for the implicit generation of field names in the implicit-naming syntactic layer, where there is a restriction on duplicate names. There has been some discussion on this issue <a href="https://srfi.schemers.org/srfi-76/mail-archive/msg00061.html">here</a>.</p><p>On the practical side, we decided not to require distinctness because it is inconvenient for a macro that calls <code>make-record-type-descriptor</code> to arrange for the names it provides to be both unique and meaningful. Moreover, a symbolic key would have to be combined with the record-type it appears in to uniquely reference a field, as disallowing duplicate field names between a record type and its extensions would break important abstraction barriers.</p><p>From a more principled perspective, the record abstractions described here don't use names as keys into record values (any more) but instead indices relative to the record type. Abstractions that do use keys could be layered on top of the facilities described here. These would certainly be useful to enable, say, pattern-matching or separate abstractions that refer to fields by name, but are outside the scope of this proposal.</p><h2>No multiple inheritance</h2><p>Multiple inheritance could be formulated as an extension of the present system, but it would raise more complex semantic and implementation issues (sharing among common parent types, among other things) than we are prepared to handle at this time.</p><h1>Examples</h1><h2>Procedural layer</h2><pre>(define :point
(make-record-type-descriptor
'point #f
#f #f #f
'((mutable x) (mutable y))))
(define make-point
(record-constructor (make-record-constructor-descriptor :point #f #f)))
(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)
(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
</pre><h2>Explicit-naming syntactic layer</h2><pre>(define-record-type (point3 make-point3 point3?)
(fields (immutable x point3-x)
(mutable y point3-y set-point3-y!))
(nongenerative point3-4893d957-e00b-11d9-817f-00111175eb9e))
(define-record-type (cpoint make-cpoint cpoint?)
(parent point3)
(protocol
(lambda (p)
(lambda (x y c)
((p x y) (color->rgb c)))))
(fields (mutable rgb cpoint-rgb cpoint-rgb-set!)))
(define (color->rgb c)
(cons 'rgb c))
(define p3-1 (make-point3 1 2))
(define p3-2 (make-cpoint 3 4 'red))
(point3? p3-1) ; => #t
(point3? p3-2) ; => #t
(point3? (vector)) ; => #f
(point3? (cons 'a 'b)) ; => #f
(cpoint? p3-1) ; => #f
(cpoint? p3-2) ; => #t
(point3-x p3-1) ; => 1
(point3-y p3-1) ; => 2
(point3-x p3-2) ; => 3
(point3-y p3-2) ; => 4
(cpoint-rgb p3-2) ; => '(rgb . red)
(set-point3-y! p3-1 17)
(point3-y p3-1) ; => 17)
(record-rtd p3-1) ; => (record-type-descriptor point3)
(define-record-type (ex1 make-ex1 ex1?)
(protocol (lambda (new) (lambda a (new a))))
(fields (immutable f ex1-f)))
(define ex1-i1 (make-ex1 1 2 3))
(ex1-f ex1-i1) ; => '(1 2 3)
(define-record-type (ex2 make-ex2 ex2?)
(protocol (lambda (new) (lambda (a . b) (new a b))))
(fields (immutable a ex2-a)
(immutable b ex2-b)))
(define ex2-i1 (make-ex2 1 2 3))
(ex2-a ex2-i1) ; => 1
(ex2-b ex2-i1) ; => '(2 3)
(define-record-type (unit-vector make-unit-vector unit-vector?)
(protocol
(lambda (new)
(lambda (x y z)
(let ((length (+ (* x x) (* y y) (* z z))))
(new (/ x length)
(/ y length)
(/ z length))))))
(fields (immutable x unit-vector-x)
(immutable y unit-vector-y)
(immutable z unit-vector-z)))
</pre><h2>Implicit-naming syntactic layer</h2><pre>(define *ex3-instance* #f)
(define-record-type ex3
(parent cpoint)
(protocol
(lambda (p)
(lambda (x y t)
(let ((r ((p x y 'red) t)))
(set! *ex3-instance* r)
r))))
(fields
(mutable thickness))
(sealed #t) (opaque #t))
(define ex3-i1 (make-ex3 1 2 17))
(ex3? ex3-i1) ; => #t
(cpoint-rgb ex3-i1) ; => '(rgb . red)
(ex3-thickness ex3-i1) ; => 17
(ex3-thickness-set! ex3-i1 18)
(ex3-thickness ex3-i1) ; => 18
*ex3-instance* ; => ex3-i1
(record? ex3-i1) ; => #f
</pre><h1>Reference implementation</h1><p>The <a href="https://srfi.schemers.org/srfi-76/srfi-76.tgz">reference implementation </a>makes use of <a href="https://srfi.schemers.org/srfi-9/">SRFI 9</a> (Defining Record Types), <a href="http://srfi.schemers.org/srfi-23/">SRFI 23</a> (Error reporting mechanism), and <a href="http://srfi.schemers.org/srfi-26/">SRFI 26</a> (Notation for Specializing Parameters without Currying) for the procedural layer and the explicit-naming syntactic layer. The implementation of the explicit-naming syntactic layer also assumes <code>letrec*</code> semantics (as specified by the upcoming R6RS) for internal definitions to support internal record-type definitions. The implicit-naming syntactic layer cannot be implemented using <code>syntax-rules</code> alone. Two implementations, one for Scheme 48 using explicit renaming, and one for PLT Scheme using <code>syntax-case</code> are provided.</p><h1>References</h1><p>Over the years, many records proposal have been advanced. This section lists only the ones that were a direct influence to this proposal.</p><p>The procedural layer of this SRFI is essentially an extension of a proposal that was considered by the R*RS authors about 15 years ago, and was supported at that time by a vote of approximately 28 to 2. On 1 September 1989, Pavel Curtis posted the proposal to rrrs-authors. Norman Adams reposted Pavel's proposal on 5 February 1992. Kent Dybvig presented an extended version of Pavel's proposal along with a syntactic interface, both developed in collaboration with Bill Rozas, at the 1998 Scheme Worshop. Pavel's proposal was also a starting point for Chez Scheme's procedural interface. The mechanism for defining and using constructor arguments in the syntactic interface is similar to the syntax used by the Scheme Widget Library for class definitions. Single inheritance was added to Larceny in 1998 and Chez Scheme in 1999, but it is likely that other implementations had inheritance before then.</p><ul><li><a href="http://www.swiss.ai.mit.edu/ftpdir/scheme-mail/HTML/rrrs-1992/msg00036.html">Pavel Curtis's 1989 proposal</a></li><li>Pavel Curtis: <a href="ftp://ftp.cs.indiana.edu/pub/scheme-repository/doc/prop/records-mvalues.ps.gz">The Scheme of Things</a>. <i>Lisp Pointers</i>, volume IV, number 1, July 1990-March 1991.</li><li><a href="http://srfi.schemers.org/srfi-9/">SRFI 9: Defining Record Types</a> by Richard A. Kelsey</li><li>The Records section in the <a href="http://www.scheme.com/csug/">Chez Scheme User's Guide</a> by R. Kent Dybvig</li><li><a href="http://www.cs.indiana.edu/chezscheme/swlman/">SWL Reference Manual</a> by Oscar Waddell</li><li>The (undocumented) <code>define-record</code> form of <a href="http://www.iro.umontreal.ca/~gambit/">Gambit-C 4.0beta</a>.</li></ul><h1>Acknowledgements</h1><p>We are grateful to Donovan Kolbly who did extensive pre-draft editing. Moreover, many members of the Scheme community who posted on the SRFI mailing list were instrumental in improving the proposal.</p><p>This SRFI was written in consultation with the other R6RS editors: Marc Feeley, Matthew Flatt, and Manuel Serrano.</p><H1>Copyright</H1>
Copyright (C) Will Clinger, R. Kent Dybvig, Michael Sperber, Anton van Straaten (2005). All Rights Reserved.
<p>
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:
<p>
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
<p>
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.
<hr>
<address>Editor: <a href="mailto:srfi minus editors at srfi dot schemers dot org">Donovan Kolbly</a></address>
</body></html>