A record is a data structure with a fixed number of components, which are called the fields of the record. Each field has a name, and access to that particular field of a record typically uses that name in some way. Ideally, the structure should provide random access to each field; in other words, one should be able to inspect the contents of any field, independently, without having to access any of the others.
For instance, an astronomical database that keeps track of information
about various stars might assemble the data into records, one record for
each star. The fields of such a record might include name, right-ascension, declination, visual-magnitude, spectral-type, and so on. Similarly, a library's card catalog includes a
record for each book or other item that the library holds, with fields like
author, title, imprint, call-number, and checked-out?.
Scheme does not provide any built-in record types, so the programmer has to
define her own. Fortunately, this is straightforward. To create a new
record type foo in Scheme, one should define a procedure to
carry out each of the following operations:
Construct and return a new record of type foo, providing space for
each field of the record and initializing any fields for which an
initialization is possible and appropriate. The procedure that performs
this operation is called a constructor for the type foo.
Given any record of type foo, recover from it the value stored in a
particular field. There should be one such procedure for each field that
exists in records of type foo. Such procedures are called selectors.
Given any Scheme value, determine whether it is a record of type foo
Procedures of this sort are called type predicates.
There's an obvious analogy between this collection of procedures and the
provision that standard Scheme makes for its built-in data structures, such
as pairs: The cons procedure is the pair constructor, car
and cdr are the selectors, and pair? is the type predicate.
One could even think of a pair as a record containing two fields.
To implement a record type in standard Scheme, one has to select an existing type and figure out how to perform the record operations using only values of the existing type. There are various ways to do this. One might, for instance, implement a record as a list in which the elements are a type tag and the successive field values, in some fixed order, thus:
;; MAKE-STAR, the constructor of STAR records
(define (make-star name right-ascension declination visual-magnitude spectral-type)
(list 'star name right-ascension declination visual-magnitude spectral-type))
;; Field selectors for STAR records
(define (star-name stella)
(list-ref stella 1))
(define (star-right-ascension stella)
(list-ref stella 2))
(define (star-declination stella)
(list-ref stella 3))
(define (star-visual-magnitude stella)
(list-ref stella 4))
(define (star-spectral-type stella)
(list-ref stella 5))
;; STAR?, the type predicate for STAR records
(define (star? object)
(and (pair? object)
(eq? (car object) 'star)
(list? (cdr object))
(= (length (cdr object)) 5)))
Here are some examples of the use of these procedures. First, let's construct a star and store it in a variable:
> (define Algol (make-star "Beta Persei" 3.13 40.93 2.66 'B8))
Print it back out immediately, just to confirm that the fields got filled in correctly.
> Algol
(star "Beta Persei" 3.13 40.93 2.66 b8)
Examine a couple of its fields separately.
> (star-visual-magnitude Algol) 2.66 > (star-spectral-type Algol) b8
Check to make sure that it satisfies the type predicate.
> (star? Algol)
#t
Some implementations of Scheme extend the standard language to provide a much simpler syntax for defining record types, saving the programmer a lot of routine work by having the computer construct the definitions of the constructor, the accessors, and the type predicate. For a long time, however, different implementations of Scheme supported record types in radically different ways. Fortunately, in the last five years, implementers have begun to support and use the syntax illustrated by the following expression:
(define-record-type star (make-star name right-ascension declination visual-magnitude spectral-type) star? (name star-name) (right-ascension star-right-ascension) (declination star-declination) (visual-magnitude star-visual-magnitude) (spectral-type star-spectral-type))
The keyword define-record-type informs Scheme that we are giving it
the information it needs to build all of the procedures for records of a
new kind. The identifier immediately after define-record-type is
the name of the new type -- in this case, star.
After that, the programmer writes out, between parentheses, the name of the
constructor and the names of the fields that she wants a record of this
type to contain. Here make-star is the name of the constructor and
the remaining five identifiers are the field names.
After that, the programmer supplies an identifier for the type predicate;
here that identifier is star?.
Finally, the programmer provides a sequence of accessor specifications. Each such specification is enclosed in parentheses and consists of two identifiers, one indicating which field is being accessed, the other the intended name for the accessor procedure for that field.
The define-record-type expression works out what the definitions of
all these procedures should look like and processes all those definitions
implicitly. After Scheme has seen the expression above, you can proceed at
once to use those procedures, thus:
> (define Algol (make-star "Beta Persei" 3.13 40.93 2.66 'B8)) > Algol #(struct:star "Beta Persei" 3.13 40.93 2.66 b8) > (star-visual-magnitude Algol) 2.66 > (star-spectral-type Algol) b8 > (star? Algol) #t
Note that when DrScheme prints a star record, it looks a little different from the version that we saw when we were using lists to implement records, above.
The define-record-type-expressions are not supported directly in
DrScheme, either, but the PLT folks have included a PLT Scheme source code
file that you can use to add this new expression type to any of the
versions of their language. You can find this file at /home/stone/courses/scheme/examples/record.ss, and you're welcome to copy
it into your home directory; alternatively, you can download it
from http://download.plt-scheme.org/scheme/plt-clean-cvs/collects/srfi/9/record.ss.
Once you have your own copy, you can put the line
(require "record.ss")
at the top of any file that needs records, and DrScheme will find the
file and set itself up to process define-record-type-expressions.
(The require form itself presupposes that you are using one of the
PLT languages, such as MzScheme or MrEd, within DrScheme; standard Scheme
under the Revised5 report on the algorithmic language Scheme does not support require, either.)
The fact that more and more implementers of Scheme are finding some way or
other to support the define-record-type mechanism described above is
not accidental. Shortly after the appearance of Revised5 report on the algorithmic language Scheme in 1998, the
leading Scheme designers, implementers, and language theorists agreed to
set up a Web site and some e-mail lists inteded specifically as a forum for
proposing new features for Scheme, evaluating such proposals, and defining
them carefully and uniformly enough to enable them to serve as the basis
for future implementations of the language. Such proposals are called
``Scheme requests for implementation,'' or SRFIs (pronounced ``surfies'').
The site, http://srfi.schemers.org/, was established in November 1998, and now contains information about fifty-two proposed extensions of the language. Seven of them were flawed in various ways and were eventually withdrawn by their authors; nine are still being debated. Most of the remaining thirty-six have been widely implemented. The PLT folks, for instance, currently support twenty-four of them, which is a fairly typical rate of adoption.
Of course, nothing requires an implementer to pay any attention to SRFIs at all. However, the advantages of standardizing extensions to the language are obvious, and most implementers feel a kind of obligation to help out Scheme programmers by enabling them to write more portable Scheme programs.
One of the pleasant features of the SRFI Web site is that you can read all
of the comments that people make about the proposals and get a sense of
what programming-language designers think about and how they work. For
instance, you might find it interesting to look at the discussion
archive for
the SRFI that proposes define-record-type-expressions; it is at http://srfi.schemers.org/srfi-9/mail-archive/maillist.html.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/scheme/records.xhtml
created April 29, 1997
last revised March 10, 2004