by Marc Nieper-Wißkirchen
This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-240@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI defines a version of
the define-record-type
definition
of R6RS
and SRFI
237 that extends the define-record-type
syntax
of R7RS
, reconciling both systems.
This SRFI is meant to be adopted by R7RS-large to integrate essentially the R6RS record system compatibly with the existing R7RS-small record system.
There have been requests to extend the SRFI 9-syntax
of define-record-type
to bring it feature-wise
on par with the R6RS syntax. At the moment, it
is unclear how to do this so that non-default constructors
can be specified in a way that naturally embraces the idea
of the SRFI 9 syntax.
In contrast to R6RS, the Scheme standard R7RS does not yet have record types that can be extended via single inheritance, can be defined procedurally, or can be inspected. Several proposals, including SRFI 99, SRFI 131, SRFI 136, and SRFI 150, have been made to add such record types to R7RS. Each of these proposals, however, is lacking in at least one of the following areas:
The proposed syntax and/or semantics are new and untested in the large.
The proposed semantics are not compatible with R7RS field names interpreted as (hygienic) identifiers.
The proposed semantics are not reconciled with R6RS.
This SRFI therefore proposes adoption of the established and
widely used R6RS record system for R7RS.
For this, the define-record-type
form of R7RS is extended (see below).
The advantages of this proposal include the following:
R7RS gets a record-type facility as powerful as the R6RS record-type facility, including single inheritance, a procedural interface, and inspection.
Porting code and sharing specifications between R6RS and R7RS is simplified.
Scheme systems supporting both R6RS and R7RS need only one implementation of record types.
In principle, the proposal works with the SRFI 9 record field names interpreted as identifiers or as symbols.
The record-type facility is known and tested in the large.
Note: Objections against the R6RS record system were were voiced by people voting against ratification of that standard's latest candidate draft. These objections touched the complexity, the role of the procedural layer, and the compatibility between the syntactic and the procedural layer of the R6RS record system. These objections can be addressed as follows:
While the complexity of the record system would have exceeded the scope and size of R7RS-small, it fits the scope and size of R7RS-large. Moreover, the refinements of SRFI 237 simplify the use of the R6RS record-type facility.
The procedural layer is intended to be used when record types have to be constructed at runtime, e.g. by interpreters that need to construct host-compatible record types. The procedural layer, much like the inspection layer, is therefore not needed for most code. Therefore, the vast majority of programmers can ignore the procedural layer.
This is strengthened by SRFI 237, which removes the restriction that the syntactic layer can only define one constructor per record type defined.
The syntactic and the procedural layer are compatible, and a syntactically defined record type can inherit from a procedurally defined record type, and vice versa. Whether a record type has been defined syntactically or procedurally is generally unobservable. To inherit from a record type, all that has to be exposed from it is either the (bound) record name or the record type descriptor (with or without a record constructor descriptor).
The latter is even simplified by SRFI 237 as the latter three entities can be used effectively interchangeably.
Both of the following record type definitions are allowed with the syntax provided by this SRFI, and are equivalent:
(define-record-type foo (make-foo x) foo? (x foo-x) (y foo-y foo-set-y!))
(define-record-type (foo make-foo foo?) (fields (immutable x foo-x) (mutable y foo-y foo-set-y!)) (protocol (lambda (new) (lambda (x) (new x (if #f #f))))))
The syntax is exported by the R6RS
libraries (srfi :240)
and (srfi :240
define-record-type)
, and by the R7RS
library (srfi 240)
.
An R7RS system supporting this SRFI should provide
the define-record-type
syntax also in
the (scheme base)
library. In this case, the
feature identifier srfi-240
should be provided.
The record mechanism described in this SRFI is based on the record mechanism described in SRFI 237, which, in turn, is based on the record mechanism of R6RS. Unless said otherwise, definitions and semantics remain unchanged from SRFI 237.
The libraries exports the auxiliary
syntax fields
, mutable
, immutable
, parent
, protocol
, sealed
, opaque
, nongenerative
, parent-rtd
and generative
identical to the exports by the same name of (srfi :237 records)
.
(define-record-type 〈name spec〉 〈record clause〉 …)
Syntax: This syntax is equivalent to the syntax of the
record-type-defining form define-record-type
exported by (srfi :237 records)
.
Semantics:
This definition is equivalent to the record-type-defining
form define-record-type
exported by (srfi :237 records)
.
(define-record-type 〈name〉
(〈constructor name〉 〈field name〉 …)
〈pred〉 〈field〉 …)
Syntax:
〈Name〉
, 〈constructor
name〉
, 〈pred〉
are
identifiers, each 〈field〉
is of the
form (〈field name〉 〈accessor
name〉)
or (〈field name〉
〈accessor name〉 〈mutator name〉)
, and
all 〈field names〉
, 〈accessor
names〉
and 〈mutator names〉
are identifiers.
It is an error for the same identifier to occur more than once as a field name.
Semantics: A new non-sealed, non-opaque and generative
base record type is generated, and the form expands into a set
of definitions in the environment where
the define-record-type
form appears.
The record type has as many fields as there
are 〈fields〉
, with the
given 〈field names〉
, in that order.
Each 〈field〉
, if of the first form, defines
an immutable field, and a mutable 〈field〉
otherwise.
〈Name〉
is defined by this definition to
the (bound) record name of the record type. If this record
name is used in the parent
clause of
another define-record-type
definition, it is an
error if that define-record-type
definition specifies no protocol
clause.
〈Constructor name〉
is bound by the
definition to a constructor for the defined record type. The
constructor takes as many arguments as there
are 〈field names〉
listed
with 〈constructor name〉
. When the
constructor is invoked, fields of the new created record whose
names are listed with 〈constructor name〉
take their (initial) values from the corresponding actual
arguments. The (initial) values of all other fields of the newly
created record are unspecified.
It is an error for a field name
to be listed with 〈constructor name〉
but
not as a field name of a field.
〈Predicate〉
is bound by the definition
to a predicate for the defined record type.
The 〈accessor names〉
and 〈mutator names〉
are bound by the
definition to the accessors and mutators, respectively, of the
corresponding fields.
The following example program runs without an error.
(import (rnrs base (6)) (except (srfi :237 records) define-record-type) (srfi :240 define-record-type)) (define-record-type foo (make-foo x) foo? (x foo-x) (y foo-y foo-set-y!)) (assert (equal? #t (foo? (make-foo 1)))) (assert (equal? 2 (foo-x (make-foo 2)))) (assert (equal? '(3 4) (let ((foo (make-foo 3))) (foo-set-y! foo 4) (list (foo-x foo) (foo-y foo))))) (define rtd (record-type-descriptor foo)) (define rcd (record-constructor-descriptor foo)) (assert (equal? 'foo (record-type-name rtd))) (assert (not (record-type-parent rtd))) (assert (record-type-generative? rtd)) (assert (not (record-type-sealed? rtd))) (assert (not (record-type-opaque? rtd))) (assert (equal? '#(x y) (record-type-field-names rtd))) (assert (not (record-field-mutable? rtd 0))) (assert (record-field-mutable? rtd 1)) (assert rcd) (define-record-type bar (parent foo) (fields z) (protocol (lambda (n) (lambda (x z) ((n x) z))))) (assert (foo? (make-bar 5 6))) (assert (equal? 5 (foo-x (make-bar 5 6)))) (assert (equal? 6 (bar-z (make-bar 5 6)))) (assert (equal? 7 (let ([bar (make-bar 5 6)]) (foo-set-y! bar 7) (foo-y bar))))
A portable implementation for R6RS systems with an implementation of SRFI 237 is in this SRFI's repository.
The Larceny Scheme implementation pioneered the idea of
a define-record-type
form that unites the syntax of
R6RS and R7RS.
The foundations of the record facility described here come from R6RS. This SRFI reuses language from R6RS.
The Larcenists: Records. Larceny User Manual, R6R6 Standard Libraries.
Michael Sperber, R. Kent Dybvig, Matthew Flatt, Anton van Straaten, Robby Findler, and Jacob Matthews: Revised6 Report on the Algorithmic Language Scheme. Journal of Functional Programming, Volume 19, Supplement S1, August 2009, pp. 1-301. DOI: 10.1017/S0956796809990074.
© 2022 Marc Nieper-Wißkirchen.
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 (including the next paragraph) 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.