Summary: Computer science is the study of data structures and algorithms. In this reading, we consider one of the key techniques for designing data structures: records.
In our exploration of Scheme, we've seen a number of data structures that allow us to organize data. A list is a dynamic data structure with a variable number of components. A vector is a data structure with a fixed number of components, each of which you can quickly access by number.
A record is a data structure with a fixed number of components, known as the the fields of the record. Each field has a name, and one uses that name to access that particular field of a record. Ideally, the structure should provide random access to each field; in other words, one should be able to inspect and possibly modify the contents of any field, independently, without taking the time 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
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
Scheme does not provide any built-in record types, so programmers have
to define their own. Fortunately, defining records is straightforward.
To create a new record type
foo in Scheme, one should define
a procedure to carry out each of the following operations:
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, 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.
foo, store in a particular field of that record a new value
obj. Procedures of this sort are called mutators. Depending on the particular application, it may be a good idea to provide a mutator for each field of a record, or for some and not others, or for none. For example, it might make sense to provide a mutator for the
checked-out?field of the record for a library book, but one would probably not want to provide a mutator for the
There's an obvious analogy between this collection of procedures and the
provision that Scheme makes for its built-in data structures
pair). In the
string, for instance, Scheme supplies a constructor
make-string), a selector (
string-ref), and a
string-set!). The only difference is that the
characters in a string are accessed by position number, so that it is
sufficient to provide just one selector and just one mutator. The fields in
a record are accessed by separate procedures, so that one must provide as
many selector procedures as there are fields and as many mutator procedures
as there are mutable fields.
In some cases it is a good idea to define some other primitives as well:
foo, where they should be counted as equal.
fooand returns another record of the same type, with the same contents.
fooand outputs a description of it and its contents, through a given output port or, if none is given, the standard output port.
To implement a record type in 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, make each record an association list -- a list of pairs, each pair having a field name as its car and the value stored in that field as its cdr. This can be implemented elegantly in Scheme, but it does not provide random access to the field values, since it is necessary to walk down the association list one pair at a time until one arrives at the pair corresponding to the field one wishes to examine.
One common and widely recommended approach that does provide random access is to use vectors as the implementation type. A record containing n fields can be identified with a vector of n + 1 elements -- the first being a symbol identifying the record type, and the subsequent ones being the values of the various fields.
For instance, in the file
you'll find a vector implementation of a record type suitable for
storing some of the chemical and physical properties of an inorganic
compound, with fields for the name and chemical formula of the compound,
its molecular weight, its melting and boiling points, and its usual color.
Here are some examples of the use of the procedures defined in that file.
First, let's construct a compound and give it a name:
> (define sample (make-compound "gadolinium iodide" "GdI3" 537.96 926 1340 'yellow))
We can print it back out immediately, just to confirm that the fields got filled in correctly.
> (compound-display sample) #compound(name: gadolinium iodide, formula: GdI3, molecular-weight: 537.96, melting-point: 926, boiling-point: 1340, color: yellow)
We can also examine a few of its fields separately.
> (get-compound-formula sample) "GdI3" > (get-compound-boiling-point sample) 1340
We can verify that it satisfies the type predicate.
> (compound? sample) #t
We can even try to build a vector that looks like the representation of a compound, but without using the constructor. However, we will find that the result does not satisfy the type predicate.
> (define attempted-fake (vector (list 'compound) "potassium fluoride" "KF" 58.10 858 1505 'colorless)) > (compound? attempted-fake) #f
We can make a copy of the compound and make sure that the copy resembles the original.
> (define clone (compound-copy sample)) > (compound? clone) #t > (get-compound-color clone) yellow
We can even check that they are identical as compounds.
> (compound=? sample clone) #t
We can modify a field of the original compound destructively and observe the effect.
> (set-compound-color! sample 'orange) > (compound-display sample) #compound(name: gadolinium iodide, formula: GdI3, molecular-weight: 537.96, melting-point: 926, boiling-point: 1340, color: orange)
Now the original and the copy are no longer identical.
> (compound=? sample clone) #f
Let's try to modify a compound to contain invalid data. As you can see, the invariants are preserved.
> (set-compound-melting-point! clone -12000) set-compound-melting-point!: The melting point of a compound must be a real number greater than -273.15. > (set-compound-color! clone -12000) set-compound-color!: The color of a compound must be a Scheme symbol. > (compound-display clone) #compound(name: gadolinium iodide, formula: GdI3, molecular-weight: 537.96, melting-point: 926, boiling-point: 1340, color: yellow)
The sorting and searching methods that we have been studying are easily
adapted to lists and vectors in which the elements are records, to be
sorted according to the values in some field. For instance, suppose that
we are given a vector of records of type
compound and asked to
arrange the records in order of ascending melting point. All we need to do
is define an appropriate comparison procedure and specialize our preferred
sorting algorithm to use it:
(define sort-by-melting-point (lambda (lst) (merge-sort lst (lambda (compound1 compound2) (<= (compound-melting-point compound1) (compound-melting-point compound2))))))
April 29, 1997 [John Stone and/or Henry Walker]
April 21, 2000 [John Stone]
Monday, 27 Movember 2000 [Sam Rebelsky]
Monday, 30 April 2001 [Samuel A. Rebelsky]
Tuesday, 26 November 2002 [Samuel A. Rebelsky]
Tuesday, 3 December 2002 [Samuel A. Rebelsky]
Monday, 28 April 2003 [Samuel A. Rebelsky]
I usually create these pages
on the fly, which means that I rarely
proofread them and they may contain bad grammar and incorrect details.
It also means that I tend to update them regularly (see the history for
more details). Feel free to contact me with any suggestions for changes.
This document was generated by
Siteweaver on Tue May 6 09:31:02 2003.
The source to the document was last modified on Mon Apr 28 23:44:58 2003.
This document may be found at