240: Reconciled Records

by Marc Nieper-Wi├čkirchen

Status

This SRFI is currently in draft 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.

Abstract

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.

Issues

Rationale

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:

This SRFI therefore proposes to adopt 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:

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:

Examples

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

Specification

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.

Example

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

Implementation

A portable implementation for R6RS systems with an implementation of SRFI 237 is in this SRFI's repository.

Acknowledgements

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.

  1. The Larcenists: Records. Larceny User Manual, R6R6 Standard Libraries.

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


Editor: Arthur A. Gleckler