Title

R6RS Records

Authors

Will Clinger, R. Kent Dybvig, Michael Sperber, Anton van Straaten

Status

This SRFI is currently in withdrawn status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-76@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Note

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.

Abstract

This SRFI describes abstractions for creating new data types representing records - data structures with named fields. This SRFI comes in four parts:

Rationale

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.

Issues

Specification

Procedural layer

(make-record-type-descriptor name 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 eqv?). When 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 #f and 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 (mutable name), or a list of the form (immutable name). 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.

(record-type-descriptor? obj)

This returns #t if the argument is a record-type descriptor, #f otherwise.

(make-record-constructor-descriptor rtd 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.

Protocol example:

(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 v ....

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.

Protocol example

(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 v ....

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.

(record-constructor constructor-descriptor)

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 record? below.

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 equal?.

If 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.

(record-predicate rtd)

Returns a procedure that, given an object obj, returns #t iff obj is a record of the type represented by rtd.

(record-accessor rtd k)

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.

(record-mutator rtd k)

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.

Explicit-Naming Syntactic Layer

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> *) (syntax)

A 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 record-type-descriptor and record-constructor-descriptor below).

<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 define-record-type form.

(fields <field-spec> *)

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.

(parent <parent name>)

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.

(protocol <exp>)

<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 parent clause.

If no 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.

(sealed #t)
(sealed #f)

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.

(opaque #t)
(opaque #f)

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.

(nongenerative <uid>)

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)))
(record-type-descriptor <record name>) (syntax)

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)))

Note that record-type-descriptor works on both opaque and non-opaque record types.

(record-constructor-descriptor <record name>) (syntax)

This evaluates to the record-constructor descriptor associated with <record-name>.

Implicit-Naming Syntactic Layer

The 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 frob?.

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.

(immutable <field name>)
(mutable <field name>)

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 frob-widget-set!.

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 widget field.

(define-record-type (frob make-frob frob?)
  (fields (mutable widget getwid setwid!))
  (protocol
    (lambda (c) (c (make-widget n)))))

Reflection

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.

Note that record? and 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 make-record-type-descriptor or record-type-descriptor it can reflect upon it.

(record? obj)

Returns #t if obj is a record, and its record type is not opaque. Returns #f otherwise.

(record-rtd rec)

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.

(record-type-name rtd)

Returns the name of the record-type descriptor rtd.

(record-type-parent rtd)

Returns the parent of the record-type descriptor rtd, or #f if it has none.

(record-type-uid rtd)

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.)

(record-type-generative? rtd)

Returns #t if rtd is generative, and #f if not.

(record-type-sealed? rtd)

Returns a boolean value indicating whether the record-type descriptor is sealed.

(record-type-opaque? rtd)

Returns a boolean value indicating whether the record-type descriptor is opaque.

(record-type-field-names rtd)

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 make-record-type-descriptor.

(record-field-mutable? rtd k)

Returns a boolean value indicating whether the field specified by k of the type represented by rtd is mutable, where k is as in record-accessor.

Design Rationale

Protocols, constructor descriptors, and constructors

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.

Non-distinct field names

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.

No multiple inheritance

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.

Examples

Procedural layer

(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

Explicit-naming syntactic layer

(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)))

Implicit-naming syntactic layer

(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

Reference implementation

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.

References

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.

Acknowledgements

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.

Copyright

Copyright (C) Will Clinger, R. Kent Dybvig, Michael Sperber, Anton van Straaten (2005). All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Donovan Kolbly