# Chapter 7. Operations on Objects

This chapter describes operations specific to Chez Scheme on nonnumeric objects, including standard objects such as pairs and numbers and Chez Scheme extensions such as boxes and records. Chapter 8 describes operations on numbers. See Chapter 6 of The Scheme Programming Language, Third Edition or the Revised5 Report on Scheme for a description of standard operations on objects.

### Section 7.1. Generic Equivalence and Type Predicates

procedure: (eqv? obj1 obj2)
procedure: (equal? obj1 obj2)
returns: see below

Chez Scheme's implementation of eqv? and equal? follow the Revised6 Report and thus differ from the Revised5 report in their treatment of certain floating point values and (for equal?) cycles.

Both predicates return #t for two inexact numbers if and only if behave precisely the same when operated upon. In particular, the IEEE floating-point values +0.0 and -0.0, which are considered numerically equal, are not considered equivalent by eqv? and equal?, since the two numbers behave differently in many circumstances. For example, (/ 1.0 0.0) evaluates to +inf.0, whereas (/ 1.0 -0.0) evaluates to -inf.0. For similar reasons, an inexact real number x is not considered equivalent to the inexact complex number with real component x and imaginary component 0.0.

The Revised6 Report on Scheme requires equal? to terminate even when given cyclic inputs and to return true if and only if the (possibly infinite) unfoldings of its arguments into regular trees are equal as ordered trees. In essence, two values are equivalent in the sense of equal? if the structure of the two values cannot be distinguished by any composition of pair and vector accessors along with the appropriate procedure (e.g., eqv? or string=?) used to compare atomic data at the leaves of the values.

(eqv? 1.0 -1.0)  #f
(eqv? 1.0 1.0+0.0i)  #f
(eqv? 1.0+0.0i 1.0+0.0i)  #t

(equal? (cons 'a 'b) (cons 'a 'b))  #t
(equal?
(cons (cons 'a 'b) (cons 'a 'b))
(let ([x (cons 'a 'b)]) (cons x x)))  #t
(equal?
(cons (cons 'a 'b) (cons 'a 'c))
(let ([x (cons 'a 'b)]) (cons x x)))  #f
(equal?
(cons (cons 'a 'b) (cons 'a 'b))
(cons 'a 'b))  #f

(define x (cons (cons 'a 'b) (cons 'a 'b)))
(define y (let ([x (cons 'a 'b)]) (cons x x)))
(equal? x y)  #t
(set-car! (car x) 'c)
(set-car! (car y) 'c)
(equal? x y)  #f

(equal? ; cyclic values
(let ([x (cons 'x 'x)])
(set-car! x x)
(set-cdr! x x)
x)
(let ([x (cons 'x 'x)])
(set-car! x x)
(set-cdr! x x)
(cons x x)))  #t

### Section 7.2. Pairs and Lists

procedure: (atom? obj)
returns: #t if obj is not a pair, #f otherwise

atom? is equivalent to (lambda (x) (not (pair? x))).

(atom? '(a b c))  #f
(atom? '(3 . 4))  #f
(atom? '())  #t
(atom? 3)  #t

procedure: (list-head list n)
returns: a list of the first n elements of list

n must be an exact nonnegative integer less than or equal to the length of list.

list-head and the standard Scheme procedure list-tail may be used together to split a list into two separate lists. While list-tail performs no allocation but instead returns a sublist of the original list, list-head always returns a copy of the first portion of the list.

list-head may be defined as follows.

(lambda (ls n)
(if (= n 0)
'()
(cons (car ls) (list-head (cdr ls) (- n 1))))))

(list-head '(a b c) 0)  ()
(list-head '(a b c) 2)  (a b)
(list-head '(a b c) 3)  (a b c)
(list-head '(a b c . d) 2)  (a b)
(list-head '(a b c . d) 3)  (a b c)
(list-head '#1=(a . #1#) 5)  (a a a a a)

procedure: (last-pair list)
returns: the last pair of a list

list must not be empty. last-pair returns the last pair (not the last element) of list. list may be an improper list, in which case the last pair is the pair containing the last element and the terminating object.

(last-pair '(a b c d))  (d)
(last-pair '(a b c . d))  (c . d)

procedure: (list-copy list)
returns: a copy of list

list-copy returns a list equal? to list, using new pairs to reform the top-level list structure.

(list-copy '(a b c))  (a b c)

(let ([ls '(a b c)])
(equal? ls (list-copy ls)))  #t

(let ([ls '(a b c)])
(let ([ls-copy (list-copy ls)])
(or (eq? ls-copy ls)
(eq? (cdr ls-copy) (cdr ls))
(eq? (cddr ls-copy) (cddr ls)))))  #f

procedure: (cons* obj ... final-obj)
procedure: (list* obj ... final-obj)
returns: a list of obj ... terminated by final-obj

If the objects obj ... are omitted, the result is simply final-obj. Otherwise, a list of obj ... is constructed, as with list, except that the final cdr field is final-obj instead of (). If final-obj is not a list, the result is an improper list.

cons* and list* are identical. The former is the Revised6 Report name.

(cons* '())  ()
(cons* '(a b))  (a b)
(cons* 'a 'b 'c)  (a b . c)
(cons* 'a 'b '(c d))  (a b c d)

procedure: (make-list n)
procedure: (make-list n obj)
returns: a list of n objs

n must be a nonnegative integer. If obj is omitted, the elements of the list are unspecified.

(make-list 0 '())  ()
(make-list 3 0)  (0 0 0)
(make-list 2 "hi")  ("hi" "hi")

procedure: (iota n)
returns: a list of integers from 0 (inclusive) to n (exclusive)

n must be an exact nonnegative integer.

(iota 0)  ()
(iota 5)  (0 1 2 3 4)

procedure: (enumerate ls)
returns: a list of integers from 0 (inclusive) to the length of ls (exclusive)

(enumerate '())  ()
(enumerate '(a b c))  (0 1 2)
(let ([ls '(a b c)])
(map cons ls (enumerate ls)))  ((a . 0) (b . 1) (c . 2))

procedure: (remq obj list)
procedure: (remv obj list)
procedure: (remove obj list)
procedure: (remq! obj list)
procedure: (remv! obj list)
procedure: (remove! obj list)
returns: a list containing the elements of list with all occurrences of obj removed

These procedures traverse the argument list, removing any objects that are equivalent to obj. The elements remaining in the output list are in the same order as they appear in the input list.

The equivalence test for remq and remq! is eq?, for remv and remv! is eqv?, and for remove and remove! is equal?.

remq!, remv! and remove! use pairs from the input list to build the output list. They perform less allocation but are not necessarily faster than their nondestructive counterparts. Their use can easily lead to confusing or incorrect results if used indiscriminately.

(remq 'a '(a b a c a d))  (b c d)
(remq 'a '(b c d))  (b c d)
(remq! 'a '(a b a c a d))  (b c d)

(remv 1/2 '(1.2 1/2 0.5 3/2 4))  (1.2 0.5 3/2 4)
(remv! #\a '(#\a #\b #\c))  (#\b #\c)

(remove '(b) '((a) (b) (c)))  ((a) (c))
(remove! '(c) '((a) (b) (c)))  ((a) (b))

procedure: (remp proc list)
returns: a list of the elements of list for which proc returns #f

proc should accept one argument and return a single value. It should not modify list.

remp applies proc to each element of list and returns a list containing only the elements for which proc returns #f. The elements of the returned list appear in the same order as they appeared in the original list.

(remp odd? '(1 2 3 4))  (2 4)
(remp
(lambda (x) (and (> x 0) (< x 10)))
'(-5 15 3 14 -20 6 0 -9))  (-5 15 14 -20 0 -9)

procedure: (filter proc list)
returns: a list of the elements of list for which proc returns true

proc should accept one argument and return a single value. It should not modify list.

filter applies proc to each element of list and returns a new list containing only the elements for which proc returns true. The elements of the returned list appear in the same order as they appeared in the original list.

(filter odd? '(1 2 3 4))  (1 3)
(filter
(lambda (x) (and (> x 0) (< x 10)))
'(-5 15 3 14 -20 6 0 -9))  (3 6)

procedure: (partition proc list)
returns: see below

proc should accept one argument and return a single value. It should not modify list.

partition applies proc to each element of list and returns two values: a new list containing only the elements for which proc returns true, and a new list containing only the elements for which proc returns #f. The elements of the returned lists appear in the same order as they appeared in the original list.

(partition odd? '(1 2 3 4))  (1 3)
(2 4)
(partition
(lambda (x) (and (> x 0) (< x 10)))
'(-5 15 3 14 -20 6 0 -9))  (3 6)
(-5 15 14 -20 0 -9)

The values returned by partition can be obtained by calling filter and remp separately, but this would require two calls to proc for each element of list.

procedure: (memp proc list)
returns: the first tail of list for whose car proc returns true, or #f

proc should accept one argument and return a single value. It should not modify list.

(memp odd? '(1 2 3 4))  (1 2 3 4)
(memp even? '(1 2 3 4))  (2 3 4)
(let ([ls (list 1 2 3 4)])
(eq? (memp odd? ls) ls))  #t
(let ([ls (list 1 2 3 4)])
(eq? (memp even? ls) (cdr ls)))  #t
(memp odd? '(2 4 6 8))  #f

procedure: (find proc list)
returns: the first element of list for which proc returns true, or #f

proc should accept one argument and return a single value. It should not modify list.

find traverses the argument list in order, applying proc to each element in turn. If proc returns a true value for a given element, find returns that element without applying proc to the remaining elements. If proc returns #f for each element of list, find returns #f.

If a program must distinguish between finding #f in the list and finding no element at all, memp should be used instead.

(find odd? '(1 2 3 4))  1
(find even? '(1 2 3 4))  2
(find odd? '(2 4 6 8))  #f
(find not '(1 a #f 55))  #f

procedure: (assp proc alist)
returns: first element of alist for whose car proc returns true, or #f

alist must be an association list. An association list is a proper list whose elements are key-value pairs of the form (key . value). proc should accept one argument and return a single value. It should not modify list.

(assp odd? '((1 . a) (2 . b)))  (1 . a)
(assp even? '((1 . a) (2 . b)))  (2 . b)
(let ([ls (list (cons 1 'a) (cons 2 'b))])
(eq? (assp odd? ls) (car ls)))  #t
(let ([ls (list (cons 1 'a) (cons 2 'b))])
(eq? (assp even? ls) (cadr ls)))  #t
(assp odd? '((2 . b)))  #f

procedure: (substq new old tree)
procedure: (substv new old tree)
procedure: (subst new old tree)
procedure: (substq! new old tree)
procedure: (substv! new old tree)
procedure: (subst! new old tree)
returns: a tree with new substituted for occurrences of old in tree

These procedures traverse tree, replacing all objects equivalent to the object old with the object new.

The equivalence test for substq and substq! is eq?, for substv and substv! is eqv?, and for subst and subst! is equal?.

substq!, substv!, and subst! perform the substitutions destructively. They perform less allocation but are not necessarily faster than their nondestructive counterparts. Their use can easily lead to confusing or incorrect results if used indiscriminately.

(substq 'a 'b '((b c) b a))  ((a c) a a)

(substv 2 1 '((1 . 2) (1 . 4) . 1))  ((2 . 2) (2 . 4) . 2)

(subst 'a
'(a . b)
'((a . b) (c a . b) . c))  (a (c . a) . c)

(let ([tr '((b c) b a)])
(substq! 'a 'b tr)
tr)  ((a c) a a)

procedure: (reverse! list)
returns: a list containing the elements of list in reverse order

reverse! destructively reverses list by reversing its links. Using reverse! in place of reverse reduces allocation but is not necessarily faster than reverse. Its use can easily lead to confusing or incorrect results if used indiscriminately.

(reverse! '())  ()
(reverse! '(a b c))  (c b a)

(let ([x '(a b c)])
(reverse! x)
x)  (a)

(let ([x '(a b c)])
(set! x (reverse! x))
x)  (c b a)

procedure: (append! list ...)
returns: the concatenation of the input lists

Like append, append! returns a new list consisting of the elements of the first list followed by the elements of the second list, the elements of the third list, and so on. Unlike append, append! reuses the pairs in all of the arguments in forming the new list. That is, the last cdr of each list argument but the last is changed to point to the next list argument. If any argument but the last is the empty list, it is essentially ignored. The final argument (which need not be a list) is not altered.

append! performs less allocation than append but is not necessarily faster. Its use can easily lead to confusing or incorrect results if used indiscriminately.

(append! '(a b) '(c d))  (a b c d)

(let ([x '(a b)])
(append! x '(c d))
x)  (a b c d)

### Section 7.3. Strings and Characters

Chez Scheme extends the standard string syntax with various character escapes. It also adds several nonstandard character names. The full set of string escapes and character names are summarized by the table below.

 string escape character equivalent description \a #\bel audible bell \b #\backspace back space \f #\page form feed \n #\newline newline \r #\return carriage return \t #\tab tab \v #\vt vertical tab \\ #\\ back slash \" #\" double quote \' #\' single quote \nnn #\nnn ascii code nnn (octal) \xnn ascii code nn (hexadecimal)

The reader signals an error for any escape sequence not listed above, e.g., for "\c".

The length and indices of a string in Chez Scheme are always fixnums.

procedure: (substring-fill! string start end char)
returns: unspecified

The characters of string from start (inclusive) to end (exclusive) are set to char. start and end must be nonnegative integers; start must be strictly less than the length of string, while end may be less than or equal to the length of string. If endstart, the string is left unchanged.

(let ([str (string-copy "a tpyo typo")])
(substring-fill! str 2 6 #\X)
str)  "a XXXX typo"

procedure: (char- char1 char2)
returns: the integer difference between char1 and char2

char- subtracts the integer value of char2 from the integer value of char1 and returns the difference. The following examples assume that the integer representation is the ASCII code for the character.

(char- #\f #\e)  1

(define digit-value
; returns the digit value of the base-r digit c, or #f if c
; is not a valid digit
(lambda (c r)
(let ([v (cond
[(char<=? #\0 c #\9) (char- c #\0)]
[(char<=? #\A c #\Z) (char- c #\7)]
[(char<=? #\a c #\z) (char- c #\W)]
[else 36])])
(and (fx< v r) v))))
(digit-value #\8 10)  8
(digit-value #\z 10)  #f
(digit-value #\z 36)  35

char- might be defined as follows.

(define char-
(lambda (c1 c2)
(- (char->integer c1) (char->integer c2))))

### Section 7.4. Vectors

Chez Scheme extends the syntax of vectors to allow the length of the vector to be specified between the # and open parenthesis, e.g., #3(a b c). If fewer elements are supplied in the syntax than the specified length, each element after the last printed element is the same as the last printed element. The length and indices of a vector in Chez Scheme are always fixnums.

procedure: (vector-copy vector)
returns: a copy of vector

vector-copy creates a new vector of the same length and contents as vector. The elements themselves are not copied.

(vector-copy '#(a b c))  #(a b c)

(let ([v '#(a b c)])
(eq? v (vector-copy v)))  #f

procedure: (vector-set-fixnum! vector n fixnum)
returns: unspecified

vector-set-fixnum! changes the nth element of vector to fixnum. n must be an exact nonnegative integer strictly less than the length of vector.

It is faster to store a fixnum than an arbitrary value, since for arbitrary values, the system has to record potential assignments from older to younger objects to support generational garbage collection. Care must be taken to ensure that the argument is indeed a fixnum, however; otherwise, the collector may not properly track the assignment. The primitive performs a fixnum check on the argument except at optimization level 3.

See also the description of fixnum-only vectors (fxvectors) below.

(let ([v (vector 1 2 3 4 5)])
(vector-set-fixnum! v 2 73)
v)  #(1 2 73 4 5)

### Section 7.5. Fixnum-only vectors

Fixnum-only vectors, or "fxvectors," are like vectors but contain only fixnums. Fxvectors are written with the #vfx prefix in place of the the # prefix for vectors, e.g., #vfx(1 2 3) or #10vfx(2). The length and indices of an fxvector are always fixnums.

Updating an fxvector is generally less expensive than updating a vector, since for vectors, the system records potential assignments from older to younger objects to support generational garbage collection. The storage management system also takes advantage of the fact that fxvectors contain no pointers to place them in an area of memory that does not have to be traced during collection.

procedure: (fxvector? obj)
returns: #t if obj is an fxvector, #f otherwise

(fxvector? '#vfx())  #t
(fxvector? '#vfx(1 2 3))  #t
(fxvector? (fxvector 1 2 3))  #t
(fxvector? '#(a b c))  #f
(fxvector? '(a b c))  #f
(fxvector? "abc")  #f

procedure: (fxvector fixnum ...)
returns: an fxvector of the fixnums fixnum ...

(fxvector)  #vfx()
(fxvector 1 3 5)  #vfx(1 3 5)

procedure: (make-fxvector n)
procedure: (make-fxvector n fixnum)
returns: an fxvector of length n

n must be a fixnum. If fixnum is supplied, each element of the fxvector is initialized to fixnum; otherwise, the elements are unspecified.

(make-fxvector 0)  #vfx()
(make-fxvector 0 7)  #vfx()
(make-fxvector 5 7)  #vfx(7 7 7 7 7)

procedure: (fxvector-length fxvector)
returns: the number of elements in fxvector

(fxvector-length '#vfx())  0
(fxvector-length '#vfx(1 2 3))  3
(fxvector-length '#10vfx(1 2 3))  10
(fxvector-length (fxvector 1 2 3 4))  4
(fxvector-length (make-fxvector 300))  300

procedure: (fxvector-ref fxvector n)
returns: the nth element (zero-based) of fxvector

n must be a fixnum strictly less than the length of fxvector.

(fxvector-ref '#vfx(-1 2 4 7) 0)  -1
(fxvector-ref '#vfx(-1 2 4 7) 1)  2
(fxvector-ref '#vfx(-1 2 4 7) 3)  7

procedure: (fxvector-set! fxvector n fixnum)
returns: unspecified

n must be a fixnum strictly less than the length of fxvector. fxvector-set! changes the nth element of fxvector to fixnum.

(let ((v (fxvector 1 2 3 4 5)))
(fxvector-set! v 2 (fx- (fxvector-ref v 2)))
v)  #vfx(1 2 -3 4 5)

procedure: (fxvector-fill! fxvector fixnum)
returns: unspecified

fxvector-fill! replaces each element of fxvector with fixnum.

(let ((v (fxvector 1 2 3)))
(fxvector-fill! v 0)
v)  #vfx(0 0 0)

procedure: (fxvector->list fxvector)
returns: a list of the elements of fxvector

(fxvector->list (fxvector))  ()
(fxvector->list '#vfx(7 5 2))  (7 5 2)

(let ((v '#vfx(1 2 3 4 5)))
(apply fx* (fxvector->list v)))  120

procedure: (list->fxvector list)
returns: an fxvector of the elements of list

list must consist entirely of fixnums.

(list->fxvector '())  #vfx()
(list->fxvector '(3 5 7))  #vfx(3 5 7)

(let ((v '#vfx(1 2 3 4 5)))
(let ((ls (fxvector->list v)))
(list->fxvector (map fx* ls ls))))  #vfx(1 4 9 16 25)

procedure: (fxvector-copy fxvector)
returns: a copy of fxvector

fxvector-copy creates a new vector of the same length and contents as fxvector.

(fxvector-copy '#vfx(3 4 5))  #vfx(3 4 5)

(let ([v '#vfx(3 4 5)])
(eq? v (fxvector-copy v)))  #f

### Section 7.6. Boxes

Boxes are single-cell objects that are primarily useful for providing an "extra level of indirection." This extra level of indirection is typically used to allow more than one body of code or data structure to share a reference, or pointer, to an object. For example, boxes may be used to implement call-by-reference semantics in an interpreter for a language employing this parameter passing discipline.

Boxes are written with the prefix #& (pronounced "hash-ampersand"). For example, #&(a b c) is a box holding the list (a b c).

procedure: (box? obj)
returns: #t if obj is a box, #f otherwise

(box? '#&a)  #t
(box? 'a)  #f
(box? (box 3))  #t

procedure: (box obj)
returns: a new box containing obj

(box 'a)  #&a
(box (box '(a b c)))  #&#&(a b c)

procedure: (unbox box)
returns: contents of box

(unbox #&a)  a
(unbox #&#&(a b c))  #&(a b c)

(let ([b (box "hi")])
(unbox b))  "hi"

procedure: (set-box! box obj)
returns: unspecified

set-box! sets the contents of box to obj.

(let ([b (box 'x)])
(set-box! b 'y)
b)  #&y

(let ([incr!
(lambda (x)
(set-box! x (+ (unbox x) 1)))])
(let ([b (box 3)])
(incr! b)
(unbox b)))  4

### Section 7.7. Symbols

procedure: (symbol=? symbol1 symbol2)
returns: #t if the two symbols are the same, #f otherwise

(symbol=? 'a 'a)  #t
(symbol=? 'a (string->symbol "a"))  #t
(symbol=? 'a 'b)  #f

procedure: (gensym)
procedure: (gensym string)
returns: a unique generated symbol

Each call to gensym returns a unique generated symbol, or gensym. Each generated symbol has two names: a "pretty" name and a globally "unique" name.

In the first form above, the pretty name is formed by combining an internal prefix with the value of an internal counter. After each name is formed, the internal counter is incremented. The parameters gensym-prefix and gensym-count, described below, may be used to access and set the internal prefix and counter. By default, the prefix is the single-character string "g". In the second form, the pretty name is given by the argument, string. The pretty name of a gensym is returned by the procedure symbol->string.

Since multiple Scheme processes may run at the same time on the same or different physical hardware, globally unique names are constructed from some combination of a unique machine identifier (such as the network address), the current process identifier (PID), and the time at which the Scheme session began, along with an internal counter. The unique name of a gensym may be obtained via the procedure gensym->unique-string.

The unique name allows gensyms to be written in such a way that they can be read back and reliably commonized on input. The syntax for gensyms includes both the pretty name and the unique name, as shown in the example below:

(gensym)  #{g0 bcsfg5eq4e9b3h9o-a}

When the parameter print-gensym is set to pretty, the printer prints the pretty name only, with a #: syntax, so

(parameterize ([print-gensym 'pretty])
(write (gensym)))

prints #:g0.

When the reader sees the #: syntax, it produces a gensym with the given pretty name, but the original unique name is lost.

When the parameter is set to #f, the printer prints just the pretty name, so

(parameterize ([print-gensym #f])
(write (gensym)))

prints g0. This is useful only when gensyms do not need to be read back in as gensyms.

In order to reduce overhead when gensyms are frequently created but rarely printed, generated pretty and unique names are not created until first requested, either by the printer or explicitly by one of the procedures symbol->string or gensym->unique-string. In addition, a gensym is not placed into the system's internal symbol table (the oblist; see page 116) until the unique name is requested. This allows gensym to be reclaimed by the storage manager if no references to the gensym exist and no unique name exists by which to access it, even if it has a top-level binding or a nonempty property list.

(define x (gensym))
x                          #{g2 bcsfg5eq4e9b3h9o-c}
(symbol->string x)         "g2"
(gensym->unique-string x)  "bcsfg5eq4e9b3h9o-c"

Gensyms subsume the notion of uninterned symbols supported by earlier versions of Chez Scheme. Similarly, the predicate uninterned-symbol? has been replaced by gensym?.

parameter: gensym-prefix
parameter: gensym-count

The parameters gensym-prefix and gensym-count are used to access and set the internal prefix and counter from which the pretty name of a gensym is generated when gensym is not given an explicit string argument. gensym-prefix defaults to the string "g" and may be set to any object. gensym-count starts at 0 and may be set to any nonnegative integer.

As described above, Chez Scheme delays the creation of the pretty name until the name is first requested by the printer or by an explicit call to symbol->string. These parameters are not consulted until that time; setting them when gensym is called thus has no effect on the generated name.

(let ([x (parameterize ([gensym-prefix "genny"]
[gensym-count 17]
[print-gensym 'pretty])
(gensym))])
(format "~s" x))                        "#{g4 bcsfg5eq4e9b3h9o-e}"
(let ([x (gensym)])
(parameterize ([gensym-prefix "genny"]
[gensym-count 17]
[print-gensym #f])
(format "~s" (gensym))))              "genny17"

procedure: (gensym->unique-string gensym)
returns: the unique name of gensym

(gensym->unique-string (gensym))  "bd3kufa7ypjcuvut-g"

procedure: (gensym? obj)
returns: #t if obj is gensym, #f otherwise

(gensym? (string->symbol "z"))  #f
(gensym? (gensym "z"))  #t
(gensym? 'a)  #f
(gensym? (gensym))  #t
(gensym? '#{g2 bcsfg5eq4e9b3h9o-c})  #t

procedure: (putprop symbol key value)
returns: unspecified

Chez Scheme associates a property list with each symbol, allowing multiple key-value pairs to be stored directly with the symbol. New key-value pairs may be placed in the property list or retrieved in a manner analogous to the use of association lists, using the procedures putprop and getprop. Property lists are often used to store information related to the symbol itself. For example, a natural language program might use symbols to represent words, using their property lists to store information about use and meaning.

putprop associates value with key on the property list of symbol. key and value may be any types of object, although key is typically a symbol.

putprop may be used to establish a new property or to change an existing property.

See the examples under getprop below.

procedure: (getprop symbol key)
procedure: (getprop symbol key default)
returns: the value associated with key on the property list of symbol

getprop searches the property list of symbol for a key identical to key (in the sense of eq?), and returns the value associated with this key, if any. If no value is associated with key on the property list of symbol, getprop returns default, or #f if the default argument is not supplied.

(putprop 'fred 'species 'snurd)
(putprop 'fred 'age 4)
(putprop 'fred 'colors '(black white))

(getprop 'fred 'species)  snurd
(getprop 'fred 'colors)  (black white)
(getprop 'fred 'nonkey)  #f
(getprop 'fred 'nonkey 'unknown)  unknown

(putprop 'fred 'species #f)
(getprop 'fred 'species 'unknown)  #f

procedure: (remprop symbol key)
returns: unspecified

remprop removes the property with key key from the property list of symbol, if such a property exists.

(putprop 'fred 'species 'snurd)
(getprop 'fred 'species)  snurd

(remprop 'fred 'species)
(getprop 'fred 'species 'unknown)  unknown

procedure: (property-list symbol)
returns: a copy of the internal property list for symbol

A property list is a list of alternating keys and values, i.e., (key value ...).

(putprop 'fred 'species 'snurd)
(putprop 'fred 'colors '(black white))
(property-list 'fred)  (colors (black white) species snurd)

procedure: (oblist)
returns: a list of interned symbols

The system maintains an internal symbol table used to insure that any two occurrences of the same symbol name resolve to the same symbol object. The oblist procedure returns a list of the symbols currently in this symbol table.

The list of interned symbols grows when a new symbol is introduced into the system or when the unique name of a gensym (see page 113) is requested. It shrinks when the garbage collector determines that it is safe to discard a symbol. It is safe to discard a symbol only if the symbol is not accessible except through the oblist, has no top-level binding, and has no properties on its property list.

(if (memq 'tiger (oblist)) 'yes 'no)  yes
(equal? (oblist) (oblist))  #t
(= (length (oblist)) (length (oblist)))  #t or #f

The first example above follows from the property that all interned symbols are in the oblist from the time they are read, which happens prior to evaluation. The second example follows from the fact that no symbols can be removed from the oblist while references to those symbols exist, in this case, within the list returned by the first call to oblist (whichever call is performed first). The expression in the third example can return #f only if a garbage collection occurs sometime between the two calls to oblist, and only if one or more symbols are removed from the oblist by that collection.

### Section 7.8. Booleans

procedure: (boolean=? boolean1 boolean2)
returns: #t if the two booleans are the same, #f otherwise

(boolean=? #t #t)  #t
(boolean=? #t #f)  #f
(boolean=? #t (< 3 4))  #t

### Section 7.9. Void

Many Scheme operations return an unspecified result. Chez Scheme typically returns a special void object when the value returned by an operation is unspecified. The Chez Scheme void object is not meant to be used as a datum, and consequently does not have a reader syntax. As for other objects without a reader syntax, such as procedures and ports, Chez Scheme output procedures print the void object using a nonreadable representation, i.e., #<void>. Since the void object should be returned only by operations that do not have "interesting" values, the default waiter printer (see waiter-write) suppresses the printing of the void object. set!, set-car!, load, and write are examples of Chez Scheme operations that return the void object.

procedure: (void)
returns: the void object

void is a procedure of no arguments that returns the void object. It can be used to force expressions that are used for effect or whose values are otherwise unspecified to evaluate to a consistent, trivial value. Since most Chez Scheme operations that are used for effect return the void object, however, it is rarely necessary to explicitly invoke the void procedure.

Since the void object is used explicitly as an "unspecified" value, it is a bad idea to use it for any other purpose or to count on any given expression evaluating to the void object.

The default waiter printer suppresses the void object; that is, nothing is printed for expressions that evaluate to the void object.

(eq? (void) #f)  #f
(eq? (void) #t)  #f
(eq? (void) '())  #f

### Section 7.10. Sorting

procedure: (list-sort predicate list)
procedure: (sort predicate list)
procedure: (sort! predicate list)
returns: a list containing the elements of list sorted according to predicate

predicate should be a procedure that expects two arguments and returns #t if its first argument must precede its second in the sorted list. That is, if predicate is applied to two elements x and y, where x appears after y in the input list, it should return true only if x should appear before y in the output list. If this constraint is met, sort and sort! perform stable sorts, i.e., two elements are reordered only when necessary according to predicate. Duplicate elements are not removed. These procedures may call predicate up to nlogn times, where n is the length of vector.

sort! performs the sort destructively, using pairs from the input list to form the output list.

list-sort and sort are identical. The former is the Revised6 Report name.

(list-sort < '(3 4 2 1 2 5))  (1 2 2 3 4 5)
(list-sort > '(0.5 1/2))  (0.5 1/2)
(list-sort > '(1/2 0.5))  (1/2 0.5))
(list->string
(list-sort char>?
(string->list "hello")))  "ollhe"

procedure: (merge predicate list1 list2)
procedure: (merge! predicate list1 list2)
returns: list1 merged with list2 in the order specified by predicate

predicate should be a procedure that expects two arguments and returns #t if its first argument must precede its second in the merged list. It should not have any side effects. That is, if predicate is applied to two objects x and y, where x is taken from the second list and y is taken from the first list, it should return true only if x should appear before y in the output list. If this constraint is met, merge and merge! are stable, in that items from list1 are placed in front of equivalent items from list2 in the output list. Duplicate elements are included in the merged list.

merge! combines the lists destructively, using pairs from the input lists to form the output list.

(merge char<?
'(#\a #\c)
'(#\b #\c #\d))  (#\a #\b #\c #\c #\d)
(merge <
'(1/2 2/3 3/4)
'(0.5 0.6 0.7))  (1/2 0.5 0.6 2/3 0.7 3/4)

procedure: (vector-sort predicate vector)
returns: a vector containing the elements of vector sorted according to predicate
procedure: (vector-sort! predicate vector)
returns: unspecified

predicate should be a procedure that expects two arguments and returns #t if its first argument must precede its second in the sorted vector. It should not have any side effects. That is, if predicate is applied to two elements x and y, where x appears after y in the input vector, The predicate should return true only if x should appear before y in the output vector. If this constraint is met, vector-sort performs a stable sort, i.e., two elements are reordered only when necessary according to predicate. Duplicate elements are not removed.

vector-sort! performs the sort destructively and does not necessarily perform a stable sort. It may call the predicate up to n2 times, where n is the length of vector.

(vector-sort < '#(3 4 2 1 2 5))  #(1 2 2 3 4 5)
(vector-sort > '#(0.5 1/2))  #(0.5 1/2)
(vector-sort > '#(1/2 0.5))  #(1/2 0.5))

(let ([v (vector 3 4 2 1 2 5)])
(vector-sort! < v)
v)  #(1 2 2 3 4 5)

### Section 7.11. Hashtables

Hashtables represent sets of associations between arbitrary Scheme values. They serve essentially the same purpose as association lists but are much faster when dealing with large numbers of associations.

When the associations all map symbols to arbitrary values, it is typically more efficient to use property lists (page 115), i.e., to add a unique property to each key (symbol) with putprop and to look up the value with getprop.

procedure: (make-eq-hashtable)
procedure: (make-eq-hashtable size)
returns: a new eq hashtable

If size is provided, it must be a nonnegative exact integer indicating approximately how many elements the hashtable should initially hold. Hashtables grow as needed, but when the hashtable grows it must rehash all of the existing elements. Providing a nonzero size can help limit the amount of rehashing that must be done.

An eq hashtable compares keys using the eq? (pointer equality) procedure and employs a hash function based on object addresses.

Because objects may be moved by the garbage collector, object addresses may change, and any operation on eq hash tables may need to rehash those objects. Because of this, it is not safe to perform concurrent hashtable operations on the same eq hash table from multiple threads.

(define ht1 (make-eq-hashtable))
(define ht2 (make-eq-hashtable 32))

procedure: (make-weak-eq-hashtable)
procedure: (make-weak-eq-hashtable size)
returns: a new weak eq hashtable

This procedure is a Chez Scheme extension. It is like make-eq-hashtable except that the keys are held weakly, i.e., they are not protected from the garbage collector. Keys reclaimed by the garbage collector are removed from the table, and their associated values are dropped the next time the table is modified, if not sooner.

(define ht1 (make-weak-eq-hashtable))
(define ht2 (make-weak-eq-hashtable 32))

procedure: (hashtable? obj)
returns: #t if obj is a hashtable, #f otherwise

(hashtable? (make-eq-hashtable))  #t
(hashtable? '(not a hash table))  #f

procedure: (eq-hashtable? obj)
returns: #t if obj is an eq hashtable, #f otherwise

(eq-hashtable? (make-eq-hashtable))  #t
(eq-hashtable? '(not a hash table))  #f

procedure: (eq-hashtable-weak? ht)
returns: #t if ht is weak, #f otherwise

This procedure is a Chez Scheme extension. ht must be an eq hashtable.

(eq-hashtable-weak? (make-eq-hashtable))  #f
(eq-hashtable-weak? (make-weak-eq-hashtable))  #t

procedure: (hashtable-mutable? ht)
returns: #t if ht is mutable, #f otherwise

ht must be a hashtable. Hashtables are normally mutable, but those created by hashtable-copy are immutable by default.

Immutable hashtables cannot be altered by any of the procedures hashtable-set!, hashtable-update!, hashtable-delete!, or hashtable-clear!. They should not be altered indirectly by modifying a pair returned by hashtable-cell.

Inaccessible keys may be dropped from an immutable weak hashtable, even though the contents of the table is otherwise unchanging. The effects of this can be observed via hashtable-keys and hashtable-entries.

(hashtable-mutable? (make-eq-hashtable))  #t
(hashtable-mutable? (hashtable-copy (make-eq-hashtable)))  #f

procedure: (hashtable-set! ht key value)
returns: unspecified

ht must be a mutable hashtable. key and value may be any Scheme values.

hashtable-set! associates the value value with the key key in ht.

(define ht (make-eq-hashtable))
(hashtable-set! ht 'a 73)

procedure: (hashtable-ref ht key default)
returns: see below

ht must be a hashtable. key and default may be any Scheme values.

hashtable-ref returns the value associated with key in ht. If no value is associated with key in ht, hashtable-ref returns default.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-set! ht p1 73)
(hashtable-ref ht p1 55)  73
(hashtable-ref ht p2 55)  55

procedure: (hashtable-contains? ht key)
returns: #t if an association for key exists in ht, #f otherwise

ht must be a hashtable. key may be any Scheme value.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-set! ht p1 73)
(hashtable-contains? ht p1)  #t
(hashtable-contains? ht p2)  #f

procedure: (hashtable-update! ht key proc default)
returns: unspecified

ht must be a mutable hashtable. key and default may be any Scheme values. proc must be a procedure and should accept one argument, should return one value, and should not modify ht.

hashtable-update! applies proc to the value associated with key in ht, or to default if no value is associated with key in ht. If proc returns, hashtable-update! changes the value associated with key in ht to the value returned by proc.

A version of hashtable-update! that does not verify that it receives arguments of the proper type might be defined as follows.

(define hashtable-update!
(lambda (ht key proc value)
(hashtable-set! ht key
(proc (hashtable-ref ht key value)))))

An implementation may, however, be able to implement hashtable-update! more efficiently by avoiding multiple hash computations and hashtable lookups.

(define ht (make-eq-hashtable))
(hashtable-update! ht 'a
(lambda (x) (* x 2))
55)
(hashtable-ref ht 'a 0)  110
(hashtable-update! ht 'a
(lambda (x) (* x 2))
0)
(hashtable-ref ht 'a 0)  220

procedure: (hashtable-cell ht key default)
returns: a pair (see below)

This procedure is a Chez Scheme extension.

ht must be a hashtable. key and default may be any Scheme values.

If no value is associated with key in ht, hashtable-cell modifies ht to associate key with default. It then returns a pair whose car is key and whose cdr is the associated value. Changing the cdr of this pair effectively updates the table to associate key with a new value. The key should not be changed.

(define ht (make-eq-hashtable))
(define v (vector 'a 'b 'c))
(define cell (hashtable-cell ht v 3))
cell  (#(a b c) . 3)
(hashtable-ref ht v 0)  3
(set-cdr! cell 4)
(hashtable-ref ht v 0)  4

procedure: (hashtable-delete! ht key)
returns: unspecified

ht must be a mutable hashtable. key may be any Scheme value.

hashtable-delete! drops any association for key from ht.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-set! ht p1 73)
(hashtable-contains? ht p1)  #t
(hashtable-delete! ht p1)
(hashtable-contains? ht p1)  #f
(hashtable-contains? ht p2)  #f
(hashtable-delete! ht p2)

procedure: (hashtable-size ht)
returns: number of entries in ht

ht must be a hashtable.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-size ht)  0
(hashtable-set! ht p1 73)
(hashtable-size ht)  1
(hashtable-delete! ht p1)
(hashtable-size ht)  0

procedure: (hashtable-copy ht)
procedure: (hashtable-copy ht mutable?)
returns: a new hashtable containing the same entries as ht

ht must be a hashtable. If mutable? is present and not false, the copy is mutable; otherwise, the copy is immutable.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(hashtable-set! ht p1 "c")
(define ht-copy (hashtable-copy ht))
(hashtable-mutable? ht-copy)  #f
(hashtable-delete! ht p1)
(hashtable-ref ht p1 #f)  #f
(hashtable-delete! ht-copy p1)  error
(hashtable-ref ht-copy p1 #f)  "c"

procedure: (hashtable-clear! ht)
procedure: (hashtable-clear! ht size)
returns: unspecified

ht must be a mutable hashtable. If size is provided, it must be a nonnegative exact integer.

hashtable-clear! removes all entries from ht. If size is provided, the hashtable is reset to the given size, as if newly created by one of the hashtable making operations with size argument size.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-set! ht p1 "first")
(hashtable-set! ht p2 "second")
(hashtable-size ht)  2
(hashtable-clear! ht)
(hashtable-size ht)  0
(hashtable-ref ht p1 #f)  #f

procedure: (hashtable-keys ht)
returns: a new vector containing the keys in ht

ht must be a hashtable.

The keys may appear in any order in the returned vector.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-set! ht p1 "one")
(hashtable-set! ht p2 "two")
(hashtable-set! ht 'q "three")
(hashtable-keys ht)  #((a . b) q (a . b))

procedure: (hashtable-entries ht)
returns: two values: a vector of keys and one of values

ht must be a hashtable.

hashtable-entries returns two values. The first is a new vector containing the keys in ht, and the second is a vector containing the corresponding values. The keys and values may appear in any order, but the order is the same for the keys and for the corresponding values.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-set! ht p1 "one")
(hashtable-set! ht p2 "two")
(hashtable-set! ht 'q "three")
(hashtable-entries ht)  #((a . b) q (a . b))
#("two" "three" "one")

procedure: (eq-hashtable-set! ht key value)
returns: unspecified

This procedure is a Chez Scheme extension.

ht must be a mutable eq hashtable. key and value may be any Scheme values.

eq-hashtable-set! associates the value value with the key key in ht.

(define ht (make-eq-hashtable))
(eq-hashtable-set! ht 'a 73)

procedure: (eq-hashtable-ref ht key default)
returns: see below

This procedure is a Chez Scheme extension.

ht must be an eq hashtable. key and default may be any Scheme values.

eq-hashtable-ref returns the value associated with key in ht. If no value is associated with key in ht, eq-hashtable-ref returns default.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(eq-hashtable-set! ht p1 73)
(eq-hashtable-ref ht p1 55)  73
(eq-hashtable-ref ht p2 55)  55

procedure: (eq-hashtable-contains? ht key)
returns: #t if an association for key exists in ht, #f otherwise

This procedure is a Chez Scheme extension.

ht must be an eq hashtable. key may be any Scheme value.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(eq-hashtable-set! ht p1 73)
(eq-hashtable-contains? ht p1)  #t
(eq-hashtable-contains? ht p2)  #f

procedure: (eq-hashtable-update! ht key proc default)
returns: unspecified

This procedure is a Chez Scheme extension.

ht must be a mutable eq hashtable. key and default may be any Scheme values. proc must be a procedure and should accept one argument, should return one value, and should not modify ht.

eq-hashtable-update! applies proc to the value associated with key in ht, or to default if no value is associated with key in ht. If proc returns, eq-hashtable-update! changes the value associated with key in ht to the value returned by proc.

A version of eq-hashtable-update! that does not verify that it receives arguments of the proper type might be defined as follows.

(define eq-hashtable-update!
(lambda (ht key proc value)
(eq-hashtable-set! ht key
(proc (eq-hashtable-ref ht key value)))))

An implementation may, however, be able to implement eq-hashtable-update! more efficiently by avoiding multiple hash computations and hashtable lookups.

(define ht (make-eq-hashtable))
(eq-hashtable-update! ht 'a
(lambda (x) (* x 2))
55)
(eq-hashtable-ref ht 'a 0)  110
(eq-hashtable-update! ht 'a
(lambda (x) (* x 2))
0)
(eq-hashtable-ref ht 'a 0)  220

procedure: (eq-hashtable-cell ht key default)
returns: a pair (see below)

This procedure is a Chez Scheme extension.

ht must be an eq hashtable. key and default may be any Scheme values.

If no value is associated with key in ht, eq-hashtable-cell modifies ht to associate key with default. It then returns a pair whose car is key and whose cdr is the associated value. Changing the cdr of this pair effectively updates the table to associate key with a new value. The key should not be changed.

(define ht (make-eq-hashtable))
(define v (vector 'a 'b 'c))
(define cell (eq-hashtable-cell ht v 3))
cell  (#(a b c) . 3)
(eq-hashtable-ref ht v 0)  3
(set-cdr! cell 4)
(eq-hashtable-ref ht v 0)  4

procedure: (eq-hashtable-delete! ht key)
returns: unspecified

This procedure is a Chez Scheme extension.

ht must be a mutable eq hashtable. key may be any Scheme value.

eq-hashtable-delete! drops any association for key from ht.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(eq-hashtable-set! ht p1 73)
(eq-hashtable-contains? ht p1)  #t
(eq-hashtable-delete! ht p1)
(eq-hashtable-contains? ht p1)  #f
(eq-hashtable-contains? ht p2)  #f
(eq-hashtable-delete! ht p2)

### Section 7.12. Records

Chez Scheme supports the definition of new data types, or record types, with fixed sets of named fields. Each new type is distinct from all other types. Records may be defined via the define-record syntactic form or via the make-record-type procedure.

The syntactic (define-record) interface is the most commonly used interface. Each define-record form defines a constructor procedure for records of the new type, a type predicate that returns true only for records of the new type, an access procedure for each field, and an assignment procedure for each mutable field. For example,

(define-record point (x y))

creates a new point record type with two fields, x and y, and defines the following procedures:

 (make-point x y) constructor (point? obj) predicate (point-x p) accessor for field x (point-y p) accessor for field y (set-point-x! p obj) mutator for field x (set-point-y! p obj) mutator for field y

The names of these procedures follow a regular naming convention by default, but the programmer can override the defaults if desired. define-record allows the programmer to control which fields are arguments to the generated constructor procedure and which are explicitly initialized by the constructor procedure. Fields are mutable by default, but may be declared immutable. Fields can generally contain any Scheme value, but the internal representation of each field may be specified, which places implicit constraints on the type of value that may be stored there. These customization options are covered in the formal description of define-record later in this section.

The procedural (make-record-type) interface may be used to implement interpreters that must handle define-record forms. Each call to make-record-type returns a record-type descriptor representing the record type. Using this record-type descriptor, programs may generate constructors, type predicates, field accessors, and field mutators dynamically. The following code demonstrates how the procedural interface might be used to create a similar point record type and associated definitions.

(define point (make-record-type "point" '(x y)))
(define make-point (record-constructor point))
(define point? (record-predicate point))
(define point-x (record-field-accessor point 'x))
(define point-y (record-field-accessor point 'y))
(define set-point-x! (record-field-mutator point 'x))
(define set-point-y! (record-field-mutator point 'y))

The procedural interface is more flexible than the syntactic interface, but this flexibility can lead to less readable programs and compromises the compiler's ability to generate efficient code. Programmers should use the syntactic interface whenever it suffices.

A record-type descriptor may also be extracted from an instance of a record type, whether the record type was produced by define-record or make-record-type, and the extracted descriptor may also be used to produce constructors, predicates, accessors, and mutators, with a few limitations noted in the description of record-type-descriptor below. This is a powerful feature that permits the coding of portable printers and object inspectors. For example, the printer employs this feature in its default record printer, and the inspector uses it to allow inspection and mutation of system- and user-defined records during debugging.

A parent record may be specified in the define-record syntax or as an optional argument to make-record-type. A new record inherits the parent record's fields, and each instance of the new record type is considered to be an instance of the parent type as well, so that accessors and mutators for the parent type may be used on instances of the new type.

Record type definitions may be classified as either generative or nongenerative. A new type results for each generative record definition, while only one type results for all occurrences of a given nongenerative record definition. This distinction is important semantically since record accessors and setters are applicable only to objects with the same type.

Syntactic (define-record) record definitions are expand-time generative by default, which means that a new record is created when the code is expanded. Expansion happens once for each form prior to compilation or interpretation, as when it is entered interactively, loaded from source, or compiled by compile-file. As a result, multiple evaluations of a single define-record form, e.g., in the body of a procedure called multiple times, always produce the same record type.

Separate define-record forms usually produce different types, even if the forms are textually identical. The only exception occurs when the name of a record is specified as a generated symbol, or gensym (page 113). Multiple copies of a record definition whose name is given by a gensym always produce the same record type; i.e., such definitions are nongenerative. Each copy of the record definition must contain the same fields and field modifiers in the same order; an error is signaled when two differing record types with the same generated name are loaded into the same Scheme process.

Procedural (make-record-type) record definitions are run-time generative by default. That is, each call to make-record-type usually produces a new record type. As with the syntactic interface, the only exception occurs when the name of the record is specified as a gensym, in which case the record type is fully nongenerative.

By default, a record is printed with the syntax

#[type-name field ...]

where field ... are the printed representations of the contents of the fields of the record, and type-name is a generated symbol, or gensym (page 113), that uniquely identifies the record type. For nongenerative records, type-name is the gensym provided by the program. Otherwise, it is a gensym whose "pretty" name (page 113) is the name given to the record by define-record or make-record-type.

The default printing of records of a given type may be overridden with record-writer.

The default syntax may be used as input to the reader as well, as long as the corresponding record type has already been defined in the Scheme session in which the read occurs. The parameter record-reader may be used to specify a different name to be recognized by the reader in place of the generated name. Specifying a different name in this manner also changes the name used when the record is printed.

The mark (#n=) and reference (#n#) syntaxes may be used within the record syntax, with the result of creating shared or cyclic structure as desired. All cycles must be resolvable, however, without mutation of an immutable record field. That is, any cycle must contain at least one pointer through a mutable field, whether it is a mutable record field or a mutable field of a built-in object type such as a pair or vector.

When the parameter print-record is set to #f, records are printed using the simpler syntax

#<record of type name>

where name is the "pretty" name of the record (not the full gensym) or the reader name first assigned to the record type.

syntax: (define-record name (fld1 ...) ((fld2 init) ...) (opt ...))
syntax: (define-record name parent (fld1 ...) ((fld2 init) ...) (opt ...))
returns: unspecified

A define-record form is a definition and may appear anywhere and only where other definitions may appear.

define-record creates a new record type containing a specified set of named fields and defines a set of procedures for creating and manipulating instances of the record type.

name must be an identifier. If name is a generated symbol (gensym), the record definition is nongenerative, otherwise it is expand-time generative. (See the discussion of generativity earlier in this section.)

Each fld must be an identifier field-name, or it must take the form

(class type field-name)

where class and type are optional and field-name is an identifier. class, if present, must be the keyword immutable or the keyword mutable. If the immutable class specifier is present, the field is immutable; otherwise, the field is mutable. type, if present, specifies how the field is represented, as described below.

 scheme-object any Scheme object integer-8 an eight-bit signed integer unsigned-8 an eight-bit unsigned integer integer-16 a 16-bit signed integer unsigned-16 a 16-bit unsigned integer integer-32 a 32-bit signed integer unsigned-32 a 32-bit unsigned integer integer-64 a 64-bit signed integer unsigned-64 a 64-bit unsigned integer single-float a 32-bit single floating point number double-float a 64-bit double floating point number

If a type is specified, the field can contain objects only of the specified type. If no type is specified, the field is of type scheme-object, meaning that it can contain any Scheme object.

The field identifiers name the fields of the record. The values of the n fields described by fld1 ... are specified by the n arguments to the generated constructor procedure. The values of the remaining fields, fld2 ..., are given by the corresponding expressions, init .... Each init is evaluated within the scope of the set of field names given by fld1 ... and each field in fld2 ... that precedes it, as if within a let* expression. Each of these field names is bound to the value of the corresponding field during initialization.

If parent is present, the record type named by parent is the parent of the record. The new record type inherits each of the parent record's fields, and records of the new type are considered records of the parent type. If parent is not present, the parent record type is a base record type with no fields.

The following procedures are defined by define-record:

• a constructor procedure whose name is make-name,

• a type predicate whose name is name?,

• an access procedure whose name is name-fieldname for each noninherited field, and

• an assignment procedure whose name is set-name-fieldname! for each noninherited mutable field.

If no parent record type is specified, the constructor behaves as if defined as

(define make-name
(lambda (id1 ...)
(let* ([id2 init] ...)
body)))

where id1 ... are the names of the fields defined by fld1 ..., id2 ... are the names of the fields defined by fld2 ..., and body builds the record from the values of the identifiers id1 ... and id2 ....

If a parent record type is specified, the parent arguments appear first, and the parent fields are inserted into the record before the child fields.

The options opt ... control the selection of names of the generated constructor, predicate, accessors, and mutators.

(constructor id)
(predicate id)
(prefix string)

The option (constructor id) causes the generated constructor's name to be id rather than make-name. The option (predicate id) likewise causes the generated predicate's name to be id rather than name?. The option (prefix string) determines the prefix to be used in the generated accessor and mutator names in place of name-.

If no options are needed, the third subexpression, (opt ...), may be omitted. If no options and no fields other than those initialized by the arguments to the constructor procedure are needed, both the second and third subexpressions may be omitted. If options are specified, the second subexpression must be present, even if it contains no field specifiers.

Here is a simple example with no inheritance and no options.

(define-record marble (color quality))
(define x (make-marble 'blue 'medium))
(marble? x)  #t
(pair? x)  #f
(vector? x)  #f
(marble-color x)  blue
(marble-quality x)  medium
(set-marble-quality! x 'low)
(marble-quality x)  low

(define-record marble ((immutable color) (mutable quality))
(((mutable shape) (if (eq? quality 'high) 'round 'unknown))))
(marble-shape (make-marble 'blue 'high))  round
(marble-shape (make-marble 'blue 'low))  unknown
(define x (make-marble 'blue 'high))
(set-marble-quality! x 'low)
(marble-shape x)  round
(set-marble-shape! x 'half-round)
(marble-shape x)  half-round

The following example illustrates inheritance.

(define-record shape (x y))
(define-record point shape ())
(define-record circle shape (radius))

(define a (make-point 7 -3))
(shape? a)  #t
(point? a)  #t
(circle? a)  #f

(shape-x a)  7
(set-shape-y! a (- (shape-y a) 1))
(shape-y a)  -4

(define b (make-circle 7 -3 1))
(shape? b)  #t
(point? b)  #f
(circle? b)  #t

(circle-radius a)  error: not of type circle

(define c (make-shape 0 0))
(shape? c)  #t
(point? c)  #f
(circle? c)  #f

This example demonstrates the use of options:

(define-record pair (car cdr)
()
((constructor cons) (prefix "")))

(define x (cons 'a 'b))
(car x)  a
(cdr x)  b
(pair? x)  #t

(pair? '(a b c))  #f
#[#{pair bdhavk1bwafxyss1-a} a b]

This example illustrates the use a specified reader name, immutable fields, and the graph mark and reference syntax.

(define-record triple ((immutable x1) (mutable x2) (immutable x3)))
(record-reader 'triple (type-descriptor triple))

(let ((t '#[triple #1=(1 2) (3 4) #1#]))
(eq? (triple-x1 t) (triple-x3 t)))  #t
(let ((x '(#1=(1 2) . #[triple #1# b c])))
(eq? (car x) (triple-x1 (cdr x))))  #t
(let ((t #[triple #1# (3 4) #1=(1 2)]))
(eq? (triple-x1 t) (triple-x3 t)))  #t
(let ((t '#1=#[triple a #1# c]))
(eq? t (triple-x2 t)))  #t
(let ((t '#1=(#[triple #1# b #1#])))
(and (eq? t (triple-x1 (car t)))
(eq? t (triple-x1 (car t)))))  #t

Cycles established with the mark and reference syntax can be resolved only if a mutable record field or mutable location of some other object is involved the cycle, as in the last two examples above. An error is signaled if only immutable fields are involved.

'#1=#[triple #1# (3 4) #1#]  error

The following example demonstrates the use of nongenerative record definitions.

(module A (point-disp)
(define-record #{point bdhavk1bwafxyss1-b} (x y))
(define square (lambda (x) (* x x)))
(define point-disp
(lambda (p1 p2)
(sqrt (+ (square (- (point-x p1) (point-x p2)))
(square (- (point-y p1) (point-y p2))))))))

(module B (base-disp)
(define-record #{point bdhavk1bwafxyss1-b} (x y))
(import A)
(define base-disp
(lambda (p)
(point-disp (make-point 0 0) p))))

(let ()
(import B)
(define-record #{point bdhavk1bwafxyss1-b} (x y))
(base-disp (make-point 3 4)))  5

This works even if the different program components are loaded from different source files or are compiled separately and loaded from different object files.

syntax: (type-descriptor name)
returns: the record-type descriptor associated with name

name must name a record type defined by define-record.

The record-type descriptor is useful for overriding the default read and write syntax using record-reader and record-writer and may also be used with the procedural interface routines described later in this section.

(define-record frob ())
(type-descriptor frob)  #<record type frob>

returns: the record-type descriptor associated with name
returns: the first name associated with rtd
procedure: (record-reader name rtd)
returns: unspecified
procedure: (record-reader name #f)
returns: unspecified
procedure: (record-reader rtd #f)
returns: unspecified

name must be a symbol, and rtd must be a record-type descriptor.

With one argument, record-reader is used to retrieve the record type associated with a name or name associated with a record type. If no association has been created, record-reader returns #f

With arguments name and rtd, record-reader registers rtd as the record-type descriptor to be used whenever the read procedure encounters a record named by name and printed in the default record syntax.

With arguments name and #f, record-reader removes any association for name to a record-type descriptor. Similarly, with arguments rtd and #f, record-reader removes any association for rtd to a name.

(define-record marble (color quality))
(define m (make-marble 'blue 'perfect))
#[#{marble bdhavk1bwafxyss1-c} blue perfect]

(record-reader (type-descriptor marble))  #f

(record-reader 'marble (type-descriptor marble))
(marble-color '#[marble red miserable])  red

(record-reader (type-descriptor marble))  marble
(record-reader 'marble)  #<record type marble>

(record-reader (type-descriptor marble) #f)
(record-reader (type-descriptor marble))  #f

(record-reader 'marble (type-descriptor marble))
(record-reader (type-descriptor marble))  #f

The introduction of a record reader also changes the default printing of records. The printer always chooses the reader name first assigned to the record, if any, in place of the unique record name, as this continuation of the example above demonstrates.

(record-reader 'marble (type-descriptor marble))
(make-marble 'pink 'splendid)  #[marble pink splendid]

procedure: (record-writer rtd)
returns: the record writer associated with rtd
procedure: (record-writer rtd proc)
returns: unspecified

rtd must be a record-type descriptor, and proc must be a procedure of three arguments, as described below.

When passed only one argument, record-writer returns the record writer associated with rtd, which is initially the default record writer for all records. The default print method prints all records in a uniform syntax that includes the generated name for the record and the values of each of the fields, as described in the introduction to this section.

When passed two arguments, record-writer establishes a new association between rtd and proc so that proc will be used by the printer in place of the default printer for records of the given type. The printer passes proc three arguments: the record r, a port p, and a procedure wr that should be used to write out the values of arbitrary Scheme objects that the print method chooses to include in the printed representation of the record, e.g., values of the record's fields.

(define-record marble (color quality))
(define m (make-marble 'blue 'medium))

#[#{marble bdhavk1bwafxyss1-d} blue medium]

(record-writer (type-descriptor marble)
(lambda (r p wr)
(display "#<" p)
(wr (marble-quality r) p)
(display " quality " p)
(wr (marble-color r) p)
(display " marble>" p)))

#<medium quality blue marble>

The record writer is used only when print-record is true (the default). When the parameter print-record is set to #f, records are printed using a compressed syntax that identifies only the type of record.

(parameterize ([print-record #f])
(format "~s" m))  "#<record of type marble>"

A print method may be called more than once during the printing of a single record to support cycle detection and graph printing (see print-graph), so print methods that perform side effects other than printing to the given port are discouraged. Whenever a print method is called more than once during the printing of a single record, in all but one call, a generic "bit sink" port is used to suppress output automatically so that only one copy of the object appears on the actual port. In order to avoid confusing the cycle detection and graph printing algorithms, a print method should always produce the same printed representation for each object. Furthermore, a print method should normally use the supplied procedure wr to print subobjects, though atomic values, such as strings or numbers, may be printed by direct calls to display or write or by other means.

(let ()
(define-record ref () ((contents 'nothing)))
(record-writer (type-descriptor ref)
(lambda (r p wr)
(display "<" p)
(wr (ref-contents r) p)
(display ">" p)))
(let ((ref-lexive (make-ref)))
(set-ref-contents! ref-lexive ref-lexive)
ref-lexive))  #0=<#0#>

Print methods need not be concerned with handling nonfalse values of the parameters print-level. The printer handles print-level automatically even when user-defined print procedures are used. Since records typically contain a small, fixed number of fields, it is usually possible to ignore nonfalse values of print-length as well.

(print-level 3)
(let ()
(define-record ref () ((contents 'nothing)))
(record-writer (type-descriptor ref)
(lambda (r p wr)
(display "<" p)
(wr (ref-contents r) p)
(display ">" p)))
(let ((ref-lexive (make-ref)))
(set-ref-contents! ref-lexive ref-lexive)
ref-lexive))  <<<<#[...]>>>>

parameter: print-record

This parameter controls the printing of records. If set to true (the default) the record writer associated with a record type is used to print records of that type. If set to false, all records are printed with the syntax #<record of type name>, where name is the name of the record type as returned by record-type-name.

procedure: (make-record-type type-name fields)
procedure: (make-record-type parent-rtd type-name fields)
returns: a record-type descriptor for a new record type

make-record-type creates a new data type and returns a record-type descriptor, a value representing the new data type. The new type is disjoint from all others.

If present, parent-rtd must be a record-type descriptor.

type-name must be a string or gensym. If type-name is a string, a new record type is generated. If type-name is a gensym, a new record type is generated only if one with the same gensym has not already been defined. If one has already been defined, the parent and fields must be identical to those of the existing record type (or an error is signaled), and the existing record type is used.

fields must be a list of field descriptors, each of which describes one field of instances of the new record type. A field descriptor is either a symbol or a list in the following form:

(class type field-name)

where class and type are optional. field-name must be a symbol. class, if present, must be the symbol immutable or the symbol mutable. If the immutable class-specifier is present, the field is immutable; otherwise, the field is mutable. type, if present, specifies how the field is represented, as described below.

 scheme-object any Scheme object integer-8 an eight-bit signed integer unsigned-8 an eight-bit unsigned integer integer-16 a 16-bit signed integer unsigned-16 a 16-bit unsigned integer integer-32 a 32-bit signed integer unsigned-32 a 32-bit unsigned integer integer-64 a 64-bit signed integer unsigned-64 a 64-bit unsigned integer single-float a 32-bit single floating point number double-float a 64-bit double floating point number

If a type is specified, the field can contain objects only of the specified type. If no type is specified, the field is of type scheme-object, meaning that it can contain any Scheme object.

The behavior of a program that modifies the string type-name or the list fields or any of its sublists is unspecified.

The record-type descriptor may be passed as an argument to any of the procedures

• record-constructor,
• record-predicate,
• record-field-accessor, and
• record-field-mutator

to obtain procedures for creating and manipulating records of the new type.

(define marble
(make-record-type "marble"
'(color quality)
(lambda (r p wr)
(display "#<" p)
(wr (marble-quality r) p)
(display " quality " p)
(wr (marble-color r) p)
(display " marble>" p))))
(define make-marble
(record-constructor marble))
(define marble?
(record-predicate marble))
(define marble-color
(record-field-accessor marble 'color))
(define marble-quality
(record-field-accessor marble 'quality))
(define set-marble-quality!
(record-field-mutator marble 'quality))
(define x (make-marble 'blue 'high))
(marble? x)  #t
(marble-quality x)  high
(set-marble-quality! x 'low)
(marble-quality x)  low
#<low quality blue marble>

The order in which the fields appear in fields is important. While field names are generally distinct, it is permissible for one field name to be the same as another in the list of fields or the same as an inherited name. In this case, field ordinals must be used to select fields in calls to record-field-accessor and record-field-mutator. Ordinals range from zero through one less than the number of fields. Parent fields come first, if any, followed by the fields in fields, in the order given.

(define r1 (make-record-type "r1" '(t t)))
(define r2 (make-record-type r1 "r2" '(t)))
(define r3 (make-record-type r2 "r3" '(t t t)))

(define x ((record-constructor r3) 'a 'b 'c 'd 'e 'f))
((record-field-accessor r3 0) x)  a
((record-field-accessor r3 2) x)  c
((record-field-accessor r3 4) x)  e
((record-field-accessor r3 't) x)  unspecified

procedure: (record-type-descriptor? obj)
returns: #t if obj is a record-type descriptor, #f otherwise

(record-type-descriptor? (make-record-type "empty" '()))  #t

(define-record marble (color quality))
(record-type-descriptor? (type-descriptor marble))
(record-type-descriptor?
(record-type-descriptor
(make-marble 'blue 'high)))  #t

procedure: (record-constructor rtd)
returns: a constructor for records of the type represented by rtd

rtd must be a record-type descriptor. The constructor accepts as many arguments as there are fields in the record; these arguments are the initial values of the fields in the order given when the record-type descriptor was created.

procedure: (record-predicate rtd)
returns: a type predicate for records of the type represented by rtd

rtd must be a record-type descriptor. The predicate accepts any value as an argument and returns #t if the value is a record of the type represented by rtd and #f otherwise.

procedure: (record-field-accessor rtd field-id)
returns: an accessor for the identified field

rtd must be a record-type descriptor, field-id must be a symbol or field ordinal, i.e., a nonnegative exact integer less than the number of fields of the given record type. The specified field must be accessible.

The generated accessor expects one argument, which must be a record of the type represented by rtd. It returns the contents of the specified field of the record.

procedure: (record-field-accessible? rtd field-id)
returns: #t if the specified field is accessible, otherwise #f

rtd must be a record-type descriptor, field-id must be a symbol or field ordinal, i.e., a nonnegative exact integer less than the number of fields of the given record type.

The compiler is free to eliminate a record field if it can prove that the field is not accessed. In making this determination, the compiler is free to ignore the possibility that an accessor might be created from a record-type descriptor obtained by calling record-type-descriptor on an instance of the record type.

procedure: (record-field-mutator rtd field-id)
returns: a mutator for the identified field

rtd must be a record-type descriptor, field-id must be a symbol or field ordinal, i.e., a nonnegative exact integer less than the number of fields of the given record type. The specified field must be mutable.

The mutator expects two arguments, r and obj. r must be a record of the type represented by rtd. obj must be a value that is compatible with the type declared for the specified field when the record-type descriptor was created. obj is stored in the specified field of the record.

procedure: (record-field-mutable? rtd field-id)
returns: #t if the specified field is mutable, otherwise #f

rtd must be a record-type descriptor, field-id must be a symbol or field ordinal, i.e., a nonnegative exact integer less than the number of fields of the given record type.

Any field declared immutable is immutable. In addition, the compiler is free to treat a field as immutable if it can prove that the field is never assigned. In making this determination, the compiler is free to ignore the possibility that a mutator might be created from a record-type descriptor obtained by calling record-type-descriptor on an instance of the record type.

procedure: (record-type-name rtd)
returns: the name of the record-type represented by rtd

rtd must be a record-type descriptor.

The name is a always a string. If a gensym is provided as the record-type name in a define-record form or make-record-type call, the result is the "pretty" name of the gensym (see 7.7).

(record-type-name (make-record-type "empty" '()))  "empty"

(define-record #{point bdhavk1bwafxyss1-b} (x y))
(define p (type-descriptor #{point bdhavk1bwafxyss1-b}))
(record-type-name p)  "point"

procedure: (record-type-symbol rtd)
returns: the generated symbol associated with rtd

rtd must be a record-type descriptor.

(define e (make-record-type "empty" '()))
(record-type-symbol e)  #{empty bdhavk1bwafxyss1-e}

(define-record #{point bdhavk1bwafxyss1-b} (x y))
(define p (type-descriptor #{point bdhavk1bwafxyss1-b}))
(record-type-symbol p)  #{point bdhavk1bwafxyss1-b}

procedure: (record-type-parent rtd)
returns: the parent record-type descriptor of rtd

rtd must be a record-type descriptor.

#f is returned if rtd has no parent record-type descriptor.

(define e (make-record-type "empty" '()))
(record-type-parent e)  #f

(define ee (make-record-type e "empty-empty" '()))
(record-type-parent ee)  #<record type empty>

procedure: (record-type-field-names rtd)
returns: a list of field names of the type represented by rtd

rtd must be a record-type descriptor. The field names are symbols.

(define-record triple ((immutable x1) (mutable x2) (immutable x3)))
(record-type-field-names (type-descriptor triple))  (x1 x2 x3)

procedure: (record-type-field-decls rtd)
returns: a list of field declarations of the type represented by rtd

rtd must be a record-type descriptor. Each field declaration has the following form:

(class type field-name)

where class, type, and field-name are as described under make-record-type.

(define-record shape (x y))
(define-record circle shape (radius))

(record-type-field-decls
(type-descriptor circle))  ((mutable scheme-object x)
(mutable scheme-object y)

procedure: (record? obj)
returns: #t if obj is a record, otherwise #f
procedure: (record? obj rtd)
returns: #t if obj is a record of the given type, otherwise #f

If present, rtd must be a record-type descriptor.

A record is "of the given type" if it is an instance of the record type or one of its ancestors. The predicate generated by record-predicate for a record-type descriptor rtd is equivalent to the following.

(lambda (x) (record? x rtd))

procedure: (record-type-descriptor rec)
returns: the record-type descriptor of rec

rec must be a record. This procedure is intended for use in the definition of portable printers and debuggers. For records created with make-record-type, it may not be the same as the descriptor returned by make-record-type. See the comments about field accessibility and mutability under record-field-accessible? and record-field-mutable? above.

(define rtd (make-record-type "frob" '(blit blat)))
rtd  #<record type frob>
(define x ((record-constructor rtd) 1 2))
(record-type-descriptor x)  #<record type frob>
(eq? (record-type-descriptor x) rtd)  unspecified

R. Kent Dybvig / Chez Scheme Version 7 User's Guide