Records

A record is a data structure with a fixed number of components, 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 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:

There's an obvious analogy between this collection of procedures and the provision that Scheme makes for its built-in data structures (vector, string, and pair). In the case of string, for instance, Scheme supplies a constructor (make-string), a selector (string-ref), a mutator (string-set!), and a type predicate (string?). 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.

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.

Here, for instance, is what a vector implementation of the star record type described above looks like:

;; The conventional name for a constructor is the name of the type with
;; MAKE- prefixed to it.

(define make-star
  (lambda (name right-ascension declination visual-magnitude spectral-type)
    (vector 'star name right-ascension declination visual-magnitude
            spectral-type))) 

;; The names of the selectors are conventionally formed from the name of
;; the type and the names of the respective fields.

(define star-name
  (lambda (s)
    (vector-ref s 1)))

(define star-right-ascension
  (lambda (s)
    (vector-ref s 2)))

(define star-declination
  (lambda (s)
    (vector-ref s 3)))

(define star-visual-magnitude
  (lambda (s)
    (vector-ref s 4)))

(define star-spectral-type
  (lambda (s)
    (vector-ref s 5)))

;; For demonstration purposes, we'll show all the possible mutators,
;; even though in most applications some or all of them would be omitted.

;; The conventional name of a mutator is like that of the corresponding
;; selector, but with SET- added at the beginning and an exclamation point
;; at the end. 

(define set-star-name!
  (lambda (s new-name)
    (vector-set! s 1 new-name)))

(define set-star-right-ascension!
  (lambda (s new-right-ascension)
    (vector-set! s 2 new-right-ascension)))

(define set-star-declination!
  (lambda (s new-declination)
    (vector-set! s 3 new-declination)))

(define set-star-visual-magnitude!
  (lambda (s new-visual-magnitude)
    (vector-set! s 4 new-visual-magnitude)))

(define set-star-spectral-type!
  (lambda (s new-spectral-type)
    (vector-set! s 5 new-spectral-type)))

;; Finally, it is conventional to form the name of the type predicate by
;; adding a question mark at the end of the name of the type.

(define star?
  (lambda (obj)
    (and (vector? obj)
         (= (vector-length obj) 6)
         (eq? (vector-ref obj 0) 'star))))

;; In many applications, it would be a good idea to add precondition tests
;; to all of these procedures except STAR?.

Here are some examples of the use of these procedures:

> (print-vector-length #f)
> (define Stella
    (make-star "Alpha Centauri 1" '(14 39 26) '(-60 49 25) -0.01 'G2))
> Stella
#(star "Alpha Centauri 1" (14 39 26) (-60 49 25) -0.01 g2)
> (star? Stella)
#t
> (star? (vector 'foo 'bar 'baz))
#f
> (star? 17)
#f
> (star-name Stella)
"Alpha Centauri 1"
> (star-visual-magnitude Stella)
-0.01
> (set-star-right-ascension! Stella '(14 39 26.2))
> Stella
#(star "Alpha Centauri 1" (14 39 26.2) (-60 49 25) -0.01 g2)

Exercise 1

Write a similar set of definitions for a record type shirt, to be used in a program that keeps track of the inventory of a clothing store. Provide fields for catalog number, size, color, price, and quantity in stock. Only the last two fields should be mutable.


Exercise 2

Define a procedure filter-by-spectral-type that takes two arguments -- a list ls of records of type star and a spectral type -- and returns a list of the names of stars of that spectral type that are elements of ls.


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 star and asked to arrange the records in order of ascending visual magnitude (that is, with the brightest stars in the lowest-numbered positions). All we need to do is define an appropriate comparison procedure and specialize our preferred sorting algorithm to use it:

(define brighter?
  (lambda (star-1 star-2)
    (< (star-visual-magnitude star-1) (star-visual-magnitude star-2))))

(define sort-by-brightness! (merge-sort! brighter?))

Exercise 3

Define a procedure that sorts a vector of records of type shirt into ascending order by catalog number.


Exercise 4

Adapt the binary search procedure so that it takes two arguments -- a vector vec of records of type shirt, sorted by the procedure defined in the previous exercise, and a catalog number catno -- and returns the entire record that contains that catalog number, if there is one in vec, or #f if there is no such record.


This document is available on the World Wide Web as

http://www.math.grin.edu/courses/Scheme/records.html

created April 29, 1997
last revised April 24, 1998

John David Stone (stone@math.grin.edu)