by Daphne Preston-Kendal
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-256@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
A SRFI 9-style define-record-type
is specified which allows subtyping while preserving encapsulation, in that the field structure of supertypes remains an implementation detail with which subtypes need not concern themselves.
SRFI 99’s syntactic layer specifies a popular extension to SRFI 9 record type definitions for single inheritance. However, the syntax used by SRFI 99 (and subsequently reduced to subsets by SRFIs 131, 136, and 150) requires record types which intend to be subtyped by the syntactic layer to make their internal field structure public. As a general matter of good programming style, implementation details should not be required to be part of a public interface. The alternative syntactic layer presented here hides the field structure of types from their subtypes: only some kind of constructor procedure needs to be exposed publically.
Moreover, an apparent conflict between SRFI 99 and the language used in the later R7RS small specification has led to an entirely bogus ‘debate’ over whether field names (field tags) in SRFI 9-style define-record-type
should be considered symbols or identifiers. (See section 14.1 of Clinger and Wand 2020.) This ‘argument’ has only become necessary because of a bad design choice in SRFI 99 whereby the names of fields are semantically significant. While both R6RS and SRFI 99 consider field names to be symbols, they do so in distinctly different ways: R6RS provides an inspection procedure to get the names of fields in a record type descriptor, but these field names are for informative purposes only; in SRFI 99, fields are uniquely identified by their symbolic names and it is by name that fields are specified in the procedural layer. By contrast, fields in R6RS are uniquely identified by their position in the record structure as an exact integer index, as demonstrated by the argument to the record-accessor
and record-mutator
procedures, and by the fact that there is no restriction in R6RS that requires field names to be unique. Moreover, SRFI 99 has a weakness because although the procedural layer promises that shadowing of supertypes’ field names within subtypes will work, it does not work at the syntactic layer, nor when creating a constructor procedure with the procedural layer that uses a custom ordering of fields. This alternative syntactic layer resolves all these issues with SRFI 99-style syntactic record definitions in a similar manner to R6RS, though considerably lighter-weight.
This specification follows the original SRFI 9 text in referring to field names as ‘field tags’.
Syntax:
(define-record-type record spec
constructor spec
predicate name
field spec ...)
Record spec
is an identifier or (identifier parent)
.
If record spec
is an identifier, the constructor spec
has the form (constructor name field tag ...)
. If record spec
has the second form, it has the form (constructor name parent instance field tag ...)
.
Field spec
s and field tag
s are as in SRFI 9 and R7RS small. There must be as many field spec
s as field tag
s within the constructor spec
, in the same order. The field tag
s in the field spec
s must be the same in the sense of bound-identifier=?
as the ones in the constructor spec
, but (unlike in R7RS small) there is no requirement for each of the field tag
s to be unique, as they are informative only.
Constructor name
and predicate name
are identifiers.
Semantics: Define-record-type
binds the identifier named by the record spec
in a unique but unspecified way that represents a record type, called the record type descriptor. The binding may be either be as a variable or as syntax. The record type is newly created upon each evaluation of a define-record-type
expression. If the record spec
includes a parent
, it must refer to another existing record type descriptor, which is taken as the descriptor for the record type which is the supertype of the newly-created record type. If there is no parent
, the newly-created record type has no supertype.
The constructor name
is defined as a variable bound to a procedure which constructs instances of the new record type. If the new record type has a supertype, the first argument to this procedure must be a direct instance of a record of the supertype (i.e. an instance of that record and not one of its subtypes), called the supertype instance. The field values of that record instance are used to fill the corresponding field values in the new record instance. The supertype instance remains independent from the newly-created instance, so that subsequent mutations to the supertype instance do not affect the corresponding fields of the newly-created subtype instance. The remaining arguments fill the field values for the fields named by the field tag
s in the define-record-type
. If there is no supertype, the arguments of the constructor procedure are simply taken to fill the fields.
The predicate name
identifier is bound to a procedure of one argument which returns #t
if its argument is an instance of the newly-created record type or of one of its future subtypes, and #f
otherwise.
The field spec
s name field accessor and mutator procedures which are bound as in SRFI 9 and R7RS small. The procedures are extended from those specifications so as to work on instances of any subtypes of the newly created record type, accessing the values that were in those fields of the supertype instance.
When Alyssa P. Hacker was a computer science student at MIT in the 1990s, she created a program to help her manage her collection of textbooks. After SRFI 9 came out in 1999, the central record type definition looked like this:
(define-record-type Book
(make-book title isbn)
book?
(title book-title)
(isbn book-isbn))
;; Alyssa took 6.001, of course ...
(define sicp-1e
(make-book "Structure and Interpretation of Computer Programs"
"0262010771"))
;; ... and was later a tutor for it ...
(define sicp-2e-instructors-manual
(make-book "Instructor’s Manual to accompany Structure and Interpretation of Computer Programs (2nd edition)"
"0262692201"))
;; She also took some elective classes:
(define baldi-iel
(make-book "An Introduction to the Indo-European Languages"
"0809310910"))
She provided this program to her friends to help them manage their own libraries of books. Ben Bitdiddle created an extension to help him manage his collection of Japanese manga and novels, and added a field to link English translations to their Japanese originals:
(define-record-type (Manga Book)
(make-manga book original-language-edition)
manga?
(original-language-edition manga-original-language-edition))
(define tensura
(make-book "転生したらスライムだった件"
"4063765784"))
(define slime
(make-manga (make-book "That Time I Got Reincarnated as a Slime"
"0316414204")
tensura))
Alyssa only considered 10-digit ISBNs when she created her program in the 1990s, but in 2007 the ISBN space was extended to 13 digits. Some ISBNs can be converted, but not all 13-digit ISBNs have 10-digit equivalents. While other countries were assigned longer ISBNs early on, the first of these ISBNs were only recently assigned to the USA, so Alyssa had to adapt her program to support them. She changes the record type definition:
(define-record-type Book
(make-book/isbn10+13 title isbn-10 isbn-13)
book?
(title book-title)
(isbn-10 book-isbn-10)
(isbn-13 book-isbn-13))
(define (make-book/isbn-10 title isbn-10)
(make-book/isbn10+13 title isbn-10 (isbn-10->isbn-13 isbn-10)))
;; compatibility procedure
(define make-book make-book/isbn-10)
(define (make-book/isbn-13 title isbn-13)
(make-book/isbn10+13 title
(if (isbn-13-convertible-to-10? isbn-13)
(isbn-13->isbn-10 isbn-10)
#f)
isbn-13))
Despite the change, Ben’s adapted version continues to work as-is and gets the new ISBN-13 support of the base record type ‘for free’, because the field structure of the Book type is an implementation detail which is hidden from his code which subtypes it.
There are two sample implementations available. The first is a completely fresh implementation of record types with inheritance, implemented on top of the record types without inheritance from R7RS small. This implementation is not remotely optimized for efficiency. Also, for obvious reasons, it is not possible to subtype a record type defined with (scheme base)
’s own define-record-type
.
The second implementation is written in terms of the R6RS record system using a trick.
Marc Nieper-Wißkirchen’s previous work on record-related libraries inspired this SRFI (but there is no reason to suppose that he approves of its contents, or otherwise).
© Daphne Preston-Kendal, 2024.
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.