<srfi minus 76 at srfi dot schemers dot org>. See instructions here to subscribe to the list.
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.
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.
This SRFI describes abstractions for creating new data types representing records - data structures with named fields. This SRFI comes in four parts:
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.
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.
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 SRFI 9: Defining Record Types, but adds several features, including single inheritance and non-generative record types.
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.
The syntax of both syntactic layers is also designed to allow future extensions by using clauses introduced by keywords.
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.
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 opaque. Thus, opacity as presented here can be used to enforce abstraction barriers.
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.
define-record-type 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.
Compared to some other record-defining forms that have been proposed and implemented, the syntax is comparatively verbose. For instance, PLT Scheme has a
define-struct form that allows record-type definitions as short as this:
(define-struct point (x y))
The Design Rationale section explains why.
It would be possible to introduce abbreviations into the syntax. In the
fields clause, a single identifier might serve as a shorthand for a
mutable field clause:
(define-record-type point (fields x y))
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
define-struct on top of this proposal.
Similarly, one might allow plain symbols to be used as field specifiers in the fields argument to
make-record-type-descriptor, defaulting to mutability or immutability.
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?
The specification of
make-record-type-descriptor has this:
If parent is not
#f, and uid is not
#f, and the parent is generative (i.e. its uid is
#f), an error is signalled.
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?
The specification of
eq? 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?
(let ((r (construct ...))) (eq? r r)) ==> #t
The behavior of
equal? on records is one of several possibilities. See the Issue EQUAL-STRUCTURE Writeup in the Common Lisp HyperSpec on why any behavior of
equal? on records is wrong for some purposes.
There is no way to use a record-type descriptor created by an explicit call to
make-record-type-descriptor as a parent type in a
define-record-type form. Should this be rectified, for example by another
define-record-type clause named
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?
Record types defined via the syntactic layer default to non-opaque. Should they default to opaque instead?
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?
Functional update and/or copy operations would be useful additions. (See this post for some discussion on the issue.) However, there are several design issues with these operations:
nongenerative clause take an expression operand rather than a symbol, as argued here?
sealed 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 here.
(make-record-type-descriptorname parent uid sealed? opaque? fields
This returns a record-type descriptor, or rtd. 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 uid is provided (see below).
The name 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.
The parent argument is either
#f or an rtd. If it is an rtd, the returned record type, t, extends the record type p represented by parent. Each record of type t is also a record of type p, and all operations applicable to a record of type p are also applicable to a record of type t, except for reflection operations if t is opaque but p is not. An error is signalled if parent is sealed (see below).
The extension relationship is transitive in the sense that a type extends its parent's parent, if any, and so on.
The uid argument is either
#f or a symbol. If it is a symbol, the created record type is non-generative, i.e. there may be only one record type with that uid in the entire system (in the sense of
make-record-type-descriptor is called repeatedly with the same uid argument (in the sense of
eq?), the parent argument must be the same in the sense of
eqv? (more on this below), and the uid, sealed?, opaque?, and fields arguments must be the same in the sense of
equal?. In this case, the same record-type descriptor (in the sense of
eqv?) is returned every time. If a call with the same uid differs in any argument, an error is signalled. If uid is
#f, or if no record type with the given uid has been created before,
make-record-type-descriptor returns a fresh record-type descriptor representing a new type disjoint from all other types.
Note: Users are strongly strongly encouraged to use symbol names constructed using the UUID namespace (for example, using the record-type name as a prefix) for the uid argument.
If parent is not
#f, and uid is not
#f, and the parent is generative (i.e. its uid is
#f), an error is signalled. In other words, the parent of a non-generative rtd must be non-generative itself.
The sealed? flag is a boolean. If true, the returned record type is sealed, i.e., it cannot be extended.
The opaque? flag is a boolean. If true, the returned record type is opaque. This means that calls to
record? will return
record-rtd (see "Reflection" below) will signal an error. The record type is also opaque if an opaque parent is supplied. If opaque? is false and an opaque parent is not supplied, the record is not opaque.
The fields argument must be a list of field specifiers. Each field specifier must be a list of the form
), or a list of the form
). The specified fields are added to the parent fields, if any, to determine the complete set of fields of the returned record type. Each name must be a symbol and names the corresponding field of the record type; the names need not be distinct. A field with tag
mutable may be modified, whereas an attempt to obtain a mutator for a field with tag
immutable will signal an error.
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.
A record type whose complete set of fields are all immutable is considered immutable itself. Conversely, a record type is considered mutable if there is at least one mutable field in its complete set of fields.
A generative record-type descriptor created by a call to
make-record-type-descriptor is not
eqv? to any record-type descriptor (generative or non-generative) created by another call to
make-record-type-descriptor. A generative record-type descriptor is
eqv? only to itself, i.e.,
(eqv? rtd1 rtd2) iff
(eq? rtd1 rtd2). Moreover:
(let ((rtd (make-record-type-descriptor ...))) (eqv? rtd rtd)) ==> #t
Note that this does not imply the following:
(let ((rtd (make-record-type-descriptor ...))) (eq? rtd rtd)) ==> #t
Also, two non-generative record-type descriptors are
eqv? iff they were successfully created by calls to
make-record-type-descriptor with the same uid arguments.
#t if the argument is a record-type descriptor,
(make-record-constructor-descriptorrtd parent-constructor-descriptor protocol
This returns a record-constructor descriptor (or constructor descriptor for short) that can be used to create record constructors (via
record-constructor; see below) or other constructor descriptors. Rtd must be a record-type descriptor. Protocol is a protocol, that describes how to initialize the fields ofrtd in the record when it is constructed. The protocol is a procedure of one parameter that must itself return a procedure, the constructor. The protocol procedure is called by
record-constructor 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 rtd with initial values.
If rtd is not an extension of another record type, then parent-constructor-descriptor must be
#f. In this case, the protocol receives as argument a procedure new that has a parameter for every field of rtd; new will return a record object with the fields of rtd initialized to its arguments.
(lambda (new) (lambda (v ...) (new v ...)))
Here, the call to
new will return a record where the fields of rtd are simply initialized with the arguments
As the protocol can be used to construct records of an extension of rtd, the record returned by new may actually be of a record type extending rtd. (See below.)
If rtd is an extension of another record type rtd', parent-constructor-descriptor itself must be a constructor descriptor of rtd' (except for default values; see below). In this case, the protocol receives as argument a procedure p whose arguments will be passed unchanged to the constructor of parent-constructor-descriptor; p will return another procedure that accepts as argument the initial values for the fields of rtd and itself returns what the constructor of parent-constructor-descriptor returned, with the field values of rtd' (and its parent and so on) initialized according to parent-constructor-descriptor and with the field values of rtd initialized according to p.
As a matter of convention, the constructor created through the protocol should always return the record object itself.
(lambda (p) (lambda (x ... v ...) (let ((construct (p x ...))) (construct v ...))))
This will initialize the fields of the parent of rtd according to parent-constructor-descriptor, calling the associated constructor with
x ... as arguments, and initializing the fields of rtd itself with
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.
Protocol can be
#f, specifying a default. This is only admissible if either rtd is not an extension of another record type, or, if it is, if parent-constructor-descriptor itself was constructed with a default protocol. In the first case, protocol will default to a procedure equivalent to the following:
(lambda (p) (lambda field-values (apply p field-values)))
In the latter case, it will default to a protocol that returns a constructor that will accept as many arguments as rtd 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.
Calls the protocol of record-constructor descriptor constructor-descriptor with an appropriate procedure c as an argument (see the description of
make-record-constructor-descriptor) that will create a record of the record type associated with constructor-descriptor.
If the record type associated with constructor-descriptoris opaque, then the values created by such a constructor are not considered by the reflection procedures to be records; see the specification of
A record from an immutable record type is called immutable; conversely, a record from a mutable record type is called mutable.
Two records created by such a constructor are equal according to
equal? iff they are
eqv?, provided their record type was not used to implement any of the types explicitly mentioned in the definition of
construct is bound to a constructor returned by
record-constructor, the following holds:
(let ((r (construct ...))) (eqv? r r)) ==> #t
For mutable records, but not necessarily for immutable ones, the following holds:
(let ((r (construct ...))) (eq? r r)) ==> #t
For mutable records, the following holds:
(let ((f (lambda () (construct ...)))) (eq? (f) (f))) => #f
For immutable records, the value of the above expression is unspecified.
Returns a procedure that, given an object obj, returns
#t iff obj is a record of the type represented by rtd.
Given a record-type descriptor rtd and an exact non-negative integer k that specifies one of the fields of rtd,
record-accessor returns a one-argument procedure that, given a record of the type represented by rtd, returns the value of the selected field of that record.
It is an error if the accessor procedure is given something other than a record of the type represented by rtd. Note that the records of the type represented by rtd include records of extensions of the type represented by rtd.
The field selected is the one corresponding the the kth element (0-based) of the fields argument to the invocation of
make-record-type-descriptor that created rtd. Note that k cannot be used to specify a field of any type rtd extends.
Given a record-type descriptor rtd and an exact non-negative integer k that specifies one of the mutable fields of rtd,
record-accessor returns a two-argument procedure that, given a record r of the type represented by rtd and an object obj, stores obj within the field of r specified by k. The k argument is as in
record-accessor. If k specifies an immutable field, an error is signalled.
The record-type-defining form
define-record-type is a
definition and can appear anywhere any other <definition> can appear.
(define-record-type<name-spec> <record clause> *
define-record-type form defines a new record type along with associated constructor descriptor and constructor, predicate, field accessors and field mutators. The
define-record-type form expands into a set of definitions in the environment where
define-record-type appears; hence, it is possible to refer to the bindings (except for that of the record-type itself) recursively.
The <name-spec> specifies the names of the record type, construction procedure, and predicate. It must take the following form.
(<record name> <constructor name> <predicate name>
<Record name>, <constructor name>, and <predicate name> must all be identifiers.
<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
<Constructor name> is defined by this definition to a constructor for the defined record type, with a protocol specified by the
protocol clause, or, in its absence, using a default value. For details, see the description of the
protocol clause below.
<Predicate name> is defined by this definition to a predicate for the defined record type.
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
where each <field-spec> has one of the following forms
immutable<field name> <accessor name>
mutable<field name> <accessor name> <mutator name>
<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>.
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.
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
define-record-type. The absence of a
parent clause implies a record type with no parent type.
<Exp> is evaluated in the same environment as the
define-record-type form, and must evaluate to a protocol appropriate for the record type being defined (see above in the description of
make-record-constructor-descriptor). 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
protocol clause is specified, a constructor descriptor is still created using a default protocol. The rules for this are the same as for
make-record-constructor-descriptor: 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.
If this option is specified, it means that the opacity of the type is the value specified as the operand. If no
sealed option is present, the defined record type is not sealed.
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
opaque option is not present, the record type is not opaque.
This specifies that the record type be nongenerative with uid <uid>, which must be an <identifier>. The absence of a
nongenerative 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.
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.
For two non-generative record-type definitions with the same uid, if the implied arguments to
make-record-type-descriptor would create an equivalent record-type descriptor, the created type is the same as the previous one. Otherwise, an error is signalled.
Note again that, in the absence of a
nongenerative clause, the question of expand-time or run-time generativity is unspecified. Specifically, the return value of the following expression in unspecified:
(let ((f (lambda (x) (define-record-type r ---) (if x r? (make-r ---))))) ((f #t) (f #f)))
This evaluates to the record-type descriptor associated with the type specified by <record-name>.
Note that, in the absense, of a
nongenerative clause, the return value of the following expression is unspecified:
(let ((f (lambda () (define-record-type r ---) (record-type-descriptor r)))) (eqv? (f) (f)))
record-type-descriptor works on both opaque and non-opaque record types.
This evaluates to the record-constructor descriptor associated with <record-name>.
define-record-type form of the implicit-naming syntactic layer is a conservative extension of the
define-record-type form of the explicit-naming layer: a
define-record-type 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.
This means that a record type defined by the
define-record-type form of either layer can be used by the other.
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
make-, and the predicate name is generated by adding a question mark (
?) to the end of the record name. For example, if the record name is
frob then the name of the construction procedure is
make-frob and the predicate name is
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.
Note that the field names with implicitly-named accessors must be distinct to avoid a conflict between the accessors.
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
-set! suffix to the accessor name. For example, if the record name is
frob and the field name is
widget, the accessor name is
frob-widget and the mutator name is
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:
(define-record-type frob (fields (mutable widget)) (protocol (lambda (c) (c (make-widget n)))))
is equivalent to the following explicit-naming layer record definition.
(define-record-type (frob make-frob frob?) (fields (mutable widget frob-widget frob-widget-set!)) (protocol (lambda (c) (c (make-widget n)))))
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
(define-record-type (frob make-frob frob?) (fields (mutable widget getwid setwid!)) (protocol (lambda (c) (c (make-widget n)))))
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.
record-rtd 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
record-type-descriptor it can reflect upon it.
#t if obj is a record, and its record type is not opaque. Returns
Returns the rtd representing the type of rec if the type is not opaque. The rtd of the most precise type is returned; that is, the type t such that rec is of type t but not of any type that extends t. If the type is opaque,
record-rtd signals an error.
Returns the name of the record-type descriptor rtd.
Returns the parent of the record-type descriptor rtd, or
#f if it has none.
Returns the uid of the record-type descriptor rtd, or
#f 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.)
#t if rtd is generative, and
#f if not.
Returns a boolean value indicating whether the record-type descriptor is sealed.
Returns a boolean value indicating whether the record-type descriptor is opaque.
Returns a list of symbols naming the fields of the type represented by rtd (not including the fields of parent types) where the fields are ordered as described under
Returns a boolean value indicating whether the field specified by k of the type represented by rtd is mutable, where k is as in
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.
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.
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.
The field names provided as an argument to
make-record-type-descriptor 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 here.
On the practical side, we decided not to require distinctness because it is inconvenient for a macro that calls
make-record-type-descriptor 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.
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.
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.
(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
(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)))
(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
The reference implementation makes use of SRFI 9 (Defining Record Types), SRFI 23 (Error reporting mechanism), and SRFI 26 (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
letrec* 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
syntax-rules alone. Two implementations, one for Scheme 48 using explicit renaming, and one for PLT Scheme using
syntax-case are provided.
Over the years, many records proposal have been advanced. This section lists only the ones that were a direct influence to this proposal.
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.
define-recordform of Gambit-C 4.0beta.
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.
This SRFI was written in consultation with the other R6RS editors: Marc Feeley, Matthew Flatt, and Manuel Serrano.
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.