237: R6RS Records (refined)

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-237@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

The record mechanism of R6RS is refined. In particular, the triad of record names, record-type descriptors and record constructor descriptors can be effectively ignored and replaced with the single notion of a record descriptor. It also removes the restriction that the syntactic layer can only define one constructor per record type defined.

Issues

Rationale

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. This SRFI addresses these objections while remaining compatible with the R6RS record system.

Record type definitions and the record types defined through them can have one or more of the following attributes in R6RS: non-generativity, sealedness, and opacity. Each has its respective raison d'être:

Examples

Record types defined by the syntactic and by the procedural layer are fully compatible and can simply inherit from each other:

(define-record-type rec1
  (fields a)
  (protocol
   (lambda (p)
     (lambda (a/2)
       (p (* 2 a/2))))))

(define rec2
  (make-record-descriptor 'rec2
    rec1 #f #f #f
    '#((immutable b))
    (lambda (n)
      (lambda (a/2 b)
        ((n a/2) b)))))
(define make-rec2 (record-constructor rec2))
(define rec2? (record-predicate rec2))
(define rec2-b (record-accessor rec2 0))

(define-record-type rec3
  (parent rec2)
  (fields c)
  (protocol
   (lambda (n)
     (lambda (c)
       ((n c c) c)))))

The following library defines a simplified abstract (read-only) dictionary type, which can appear as a dictionary based on a hash table or on an alist. For this, it defines one record type but with several names:

(library (example dictionary)
  (export dictionary dictionary? dictionary-ref
          dictionary-from-hashtable make-dictionary-from-hashtable
          dictionary-from-alist make-dictionary-from-alist)
  (import (rnrs base (6))
          (rnrs hashtables (6))
          (srfi :237))

  (define-record-type dictionary
    (nongenerative) (opaque #t)
    (fields ht)
    (protocol
     (lambda (p)
       (lambda args
         (assert #f)))))

  (define dictionary-ref
    (lambda (dict key default)
      (assert (dictionary? key))
      (hashtable-ref (dictionary-ht dict) key default)))

  (define-record-name (dictionary-from-hashtable dictionary)
    (protocol
     (lambda (p)
       (lambda (ht)
         (assert (hashtable? ht))
         (p ht)))))

  (define-record-name (dictionary-from-alist dictionary)
    (protocol
     (lambda (p)
       (lambda (alist)
         (define ht (make-eqv-hashtable))
         (assert (list? alist))
         (for-each
          (lambda (entry)
            (assert (pair? entry))
            (hashtable-set! ht (car entry) (cdr entry)))
          alist)
         (p ht))))))

This library can be used to define child record types based on either appearance:

(define-record-type owned-dictionary
  (parent dictionary)
  (fields owner)
  (protocol
   (lambda (n)
     (lambda args
       (assert #f)))))

(define-record-name (owned-dictionary-from-hashtable owned-dictionary)
  (parent dictionary-from-hashtable)
  (protocol
   (lambda (n)
     (lambda (ht owner)
       ((n ht) owner)))))

(define-record-name (owned-dictionary-from-alist owned-dictionary)
  (parent dictionary-from-alist)
  (protocol
   (lambda (n)
     (lambda (alist owner)
       ((n alist) owner)))))

Specification

The record mechanism spans three R6RS libraries:

The (srfi :237) and (srfi :237 records) libraries are each a composite of these three libraries. This composite library exports all procedures and syntactic forms provided by the component libraries.

The corresponding R7RS library names are (srfi 237 syntactic), (srfi 237 procedural), (srfi 237 inspection), and (srfi 237), respectively.

The record mechanism described in this SRFI is based on the record mechanism described in R6RS. Unless said otherwise, definitions and semantics remain unchanged from R6RS.

A record descriptor is what is called a record constructor descriptor in R6RS. The term record constructor descriptor is deprecated.

The naming convention rd in the procedure entries below implies that the type of the argument must be a record descriptor.

The type of record descriptors is a subtype of the type of record-type descriptors. A record-type descriptor that is not a record descriptor is a simple record-type descriptor. Each record descriptor has an underlying simple record-type descriptor and an underlying parent descriptor. The underlying simple record-type descriptor of a simple record-type descriptor is the simple record-type descriptor itself. The underlying parent descriptor of a record descriptor representing a base record type is #f.

Whenever a syntax or procedure described below expects a record-type descriptor, the result is equivalent to when the record-type descriptor is replaced by its underlying simple record-type descriptor.

Note: Conceptually, a record descriptor is a simple record-type descriptor together with an R6RS record constructor descriptor whose associated record type is represented by the simple record-type descriptor.

Note: Most users can safely ignore the notion of an underlying simple record-type descriptor and just use record descriptors.

Syntactic layer

The syntactic layer is provided by the (srfi :237 records syntactic) library.

Syntax

The library exports the auxiliary syntax fields, mutable, immutable, parent, protocol, sealed, opaque, nongenerative, parent-rtd identical to the exports by the same name of (rnrs records syntactic (6)). It also exports the auxiliary syntax generative.

(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 (rnrs records syntactic (6)).

Semantics: This definition is equivalent to the record-type-defining form define-record-type exported by (rnrs records syntactic (6)) with the followings additions:

The ⟨record name⟩ is bound to a record name, which is a keyword. As an expression, this keyword evaluates to the underlying record descriptor whose underlying simple record-type descriptor is the simple record-type descriptor associated with the type specified by ⟨record name⟩ and whose underlying parent descriptor is the record descriptor of the parent of the type, or #f in case of a base type.

Note: R6RS allows that ⟨record name⟩ is bound to an expand-time or run-time representation.

The ⟨name spec⟩ can also be of the form (⟨rtd name⟩ ⟨record name⟩ ⟨constructor name⟩ ⟨predicate name⟩) or (⟨rtd name⟩ ⟨record name⟩). In these cases, ⟨rtd name⟩, instead ⟨record name⟩, taken as a symbol, becomes the name of the record type. The second form is an abbreviation for the first form where the constructor and the predicate name is derived from the ⟨rtd name⟩, instead of the ⟨record name⟩.

In a parent clause, the ⟨parent name⟩ can be either a record name or an expression that must evaluate to a record-type descriptor or record descriptor. If ⟨parent name⟩ is a record name, the parent clause is equivalent to a parent clause of the R6RS syntactic layer.

Otherwise, if the expression evaluates to a record-type descriptor rtd, the parent clause is equivalent to the clause (parent-rtd rtd #f) of the R6RS syntactic layer.

Finally, if the expression evaluates to a record descriptor rd, the parent clause is equivalent to the clause (parent-rtd rd rd) of the R6RS syntactic layer.

The parent-rtd clause is deprecated.

A clause of the (generative) specifies that the record type is generative. This clause is mutually exclusive to a non-generative clause.

Note: While this clause is superfluous as its absence (and the absence of a non-generative clause) also specifies that the record type is generative, it makes it sensible to encourage implementations to raise a continuable exception of type &warning when a define-record-type with neither a generative nor non-generative clause is expanded. This helps detecting accidental specifications of generative record types.

Note: An implementation is also encouraged to raise a continuable exception of type &warning when a non-generative clause without a ⟨uid⟩ is specified as such record types are not compatible across different expansions of a define-record-type form, e.g. when a library is visited more than once.

Remark: In Chez Scheme, the equivalent of the clause (generative) is a clause of the form (nongenerative #f). This syntax is not used in this SRFI to avoid double-negation and misunderstanding because #f often stands for a default value, which, in this context, could be a generate uid.

(define-record-name ⟨name spec⟩ ⟨record clause⟩ …)

Syntax: The ⟨name spec⟩ is of the form (⟨record name⟩ ⟨record type⟩ ⟨constructor name⟩) or (⟨record name⟩ ⟨record type⟩). The ⟨record name⟩ must be an identifier, the ⟨record type⟩ a record name or an expression.

The second form of ⟨name spec⟩ is an abbreviation for the first form, where the name of the constructor is generated by prefixing the record name with make-.

A ⟨record clause⟩ is as for the define-record-type syntax except that only a parent and a protocol clause is allowed.

Semantics: If ⟨record type⟩ is bound to a record name (e.g. by a previous define-record-type or define-record-name definition), let rd be the underlying record descriptor. Otherwise, ⟨record type⟩ must evaluate to a record descriptor rd.

This definition binds the ⟨record name⟩ to a record name as defined in the description of the define-record-type definition. The underlying record descriptor is one whose underlying simple record-type descriptor is the simple record-type descriptor underlying rd and whose underlying parent descriptor is the record descriptor of the parent, or the underlying parent descriptor of rd.

The protocol clause specifies the constructor descriptor of the record descriptor underlying ⟨record name⟩.

(record-type-descriptor ⟨record name⟩)

Equivalent to the syntax with the same name in R6RS.

The record-type-descriptor syntax is deprecated.

Note: Use (record-descriptor-rtd ⟨record name⟩) instead.

(record-constructor-descriptor ⟨record name⟩)

Equivalent to the syntax with the same name in R6RS.

The record-constructor-descriptor syntax is deprecated.

Note: Use (values ⟨record name⟩) instead.

Procedural layer

The syntactic layer is provided by the (srfi :237 records procedural) library.

Procedures

(make-record-type-descriptor name parent uid sealed? opaque? fields)

Equivalent to the procedure with the same name in R6RS. The returned record-type descriptor is a simple record-type descriptor.

(record-type-descriptor? obj)

Equivalent to the procedure with the same name in R6RS.

(make-record-descriptor rtd parent-descriptor protocol)

(make-record-constructor-descriptor rtd parent-descriptor protocol)

Equivalent to the procedure with the latter name in R6RS. The underlying simple record-type descriptor of the returned record descriptor is the underlying simple record-type descriptor of rtd. The underlying parent descriptor of the record descriptor is the parent-descriptor.

The name make-record-constructor-descriptor is deprecated.

(make-record-descriptor name parent uid sealed? opaque? fields protocol)

Equivalent to (make-record-descriptor (make-record-type-descriptor name parent uid sealed? opaque? fields) parent protocol).

(record-descriptor-rtd rd)

Returns the underlying simple record-type descriptor of rd.

(record-descriptor-parent rd)

Returns the underlying parent record descriptor of rd.

(record-descriptor? obj)

(record-constructor-descriptor? obj)

Returns #t if the argument is a record descriptor, #f otherwise.

Note: This predicate is missing in R6RS. According to at least one of the editors, it shouldn't.

The name record-constructor-descriptor? is deprecated.

(record-constructor rd)

Equivalent to the procedure with the same name in R6RS.

(record-predicate rtd k)

Equivalent to the procedure with the same name in R6RS.

(record-accessor rtd k)

Equivalent to the procedure with the same name in R6RS.

(record-mutator rtd k)

Equivalent to the procedure with the same name in R6RS.

Inspection layer

The syntactic layer is provided by the (srfi :237 records inspection) library.

Procedures

(record? obj)

Equivalent to the procedure with the same name in R6RS.

(record-rtd record)

Equivalent to the procedure with the same name in R6RS.

(record-type-name rtd)

Equivalent to the procedure with the same name in R6RS.

(record-type-parent rtd)

Equivalent to the procedure with the same name in R6RS.

(record-type-uid rtd)

Equivalent to the procedure with the same name in R6RS.

(record-type-generative? rtd)

Equivalent to the procedure with the same name in R6RS.

(record-type-sealed? rtd)

Equivalent to the procedure with the same name in R6RS.

(record-type-opaque? rtd)

Equivalent to the procedure with the same name in R6RS.

(record-type-field-names rtd)

Equivalent to the procedure with the same name in R6RS.

(record-field-mutable? rtd k)

Equivalent to the procedure with the same name in R6RS.

Implementation

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

Acknowledgements

The foundations of the record facility described here come from R6RS. This SRFI reuses language from R6RS.

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