Next: , Previous: Reader & writer, Up: System facilities


4.7 Records

Scheme48 provides several different levels of a record facility. Most programmers will probably not care about the two lower levels; the syntactic record type definers are sufficient for abstract data types.

At the highest level, there are two different record type definition macros. Richard Kelsey's is exported from the defrecord structure; Jonathan Rees's is exported from define-record-types. They both export a define-record-type macro and the same define-record-discloser procedure; however, the macros are dramatically different. Scheme48 also provides [SRFI 9], which is essentially Jonathan Rees's record type definition macro with a slight syntactic difference, in the srfi-9 structure. Note, however, that srfi-9 does not export define-record-discloser. The difference between Jonathan Rees's and Richard Kelsey's record type definition macros is merely syntactic convenience; Jonathan Rees's more conveniently allows for arbitrary naming of the generated variables, whereas Richard Kelsey's is more convenient if the naming scheme varies little.

4.7.1 Jonathan Rees's define-record-type macro

— syntax: define-record-type
          (define-record-type record-type-name record-type-variable
            (constructor constructor-argument ...)
            [predicate]
            (field-tag field-accessor [field-modifier])
            ...)

This defines record-type-variable to be a record type descriptor. Constructor is defined to be a procedure that accepts the listed field arguments and creates a record of the newly defined type with those fields initialized to the corresponding arguments. Predicate, if present, is defined to be the disjoint (as long as abstraction is not violated by the lower-level record interface) type predicate for the new record type. Each field-accessor is defined to be a unary procedure that accepts a record type and returns the value of the field named by the corresponding field-tag. Each field-modifier, if present, is defined to be a binary procedure that accepts a record of the new type and a value, which it assigns the field named by the corresponding field-tag to. Every constructor-argument must have a corresponding field-tag, though field-tags that are not used as arguments to the record type's constructor are simply uninitialized when created. They should have modifiers: otherwise they will never be initialized.

It is worth noting that Jonathan Rees's define-record-type macro does not introduce identifiers that were not in the original macro's input form.

For example:

          (define-record-type pare rtd/pare
            (kons a d)
            pare?
            (a kar)
            (d kdr set-kdr!))
          
          (kar (kons 5 3))
              => 5
          
          (let ((p (kons 'a 'c)))
            (set-kdr! p 'b)
            (kdr p))
              => b
          
          (pare? (kons 1 2))
              => #t
          
          (pare? (cons 1 2))
              => #f

There is also a variant of Jonathan Rees's define-record-type macro for defining record types with fields whose accessors and modifiers respect optimistic concurrency by logging in the current proposal.

4.7.2 Richard Kelsey's define-record-type macro

— syntax: define-record-type
          (define-record-type type-name
            (argument-field-specifier ...)
            (nonargument-field-specifier ...))
          
              argument-field-specifier -->
                  field-tag              Immutable field
                | (field-tag)            Mutable field
              nonargument-field-specifier -->
                  field-tag              Uninitialized field
                | (field-tag exp)        Initialized with exp's value

This defines type/type-name to be a record type descriptor for the newly defined record type, type-name-maker to be a constructor for the new record type that accepts arguments for every field in the argument field specifier list, type-name? to be the disjoint type predicate for the new record type, accessors for each field tag field-tag by constructing an identifier type-name-field-tag, and modifiers for each argument field tag that was specified to be mutable as well as each nonargument field tag. The name of the modifier for a field tag field-tag is constructed to be set-type-name-field-tag!.

Note that Richard Kelsey's define-record-type macro does concatenate & introduce new identifiers, unlike Jonathan Rees's.

For example, a use of Richard Kelsey's define-record-type macro

          (define-record-type pare
            (kar
             (kdr))
            (frob
             (mumble 5)))

is equivalent to the following use of Jonathan Rees's macro

          (define-record-type pare type/pare
            (%pare-maker kar kdr mumble)
            pare?
            (kar pare-kar)
            (kdr pare-kdr set-pare-kdr!)
            (frob pare-frob set-pare-frob!)
            (mumble pare-mumble set-pare-mumble!))
          
          (define (pare-maker kar kdr)
            (%pare-maker kar kdr 5))

4.7.3 Record types

Along with two general record type definition facilities, there are operations directly on the record type descriptors themselves, exported by the record-types structure. (Record type descriptors are actually records themselves.)

— procedure: make-record-type name field-tags –> record-type-descriptor
— procedure: record-type? object –> boolean

Make-record-type makes a record type descriptor with the given name and field tags. Record-type? is the disjoint type predicate for record types.

— procedure: record-type-name rtype-descriptor –> symbol
— procedure: record-type-field-names rtype-descriptor –> symbol-list

Accessors for the two record type descriptor fields.

— procedure: record-constructor rtype-descriptor argument-field-tags –> constructor-procedure
— procedure: record-predicate rtype-descriptor –> predicate-procedure
— procedure: record-accessor rtype-descriptor field-tag –> accessor-procedure
— procedure: record-modifier rtype-descriptor field-tag –> modifier-procedure

Constructors for the various procedures relating to record types. Record-constructor returns a procedure that accepts arguments for each field in argument-field-tags and constructs a record whose record type descriptor is rtype-descriptor, initialized with its arguments. Record-predicate returns a disjoint type predicate for records whose record type descriptor is rtype-descriptor. Record-accessor and record-modifier return accessors and modifiers for records whose record type descriptor is rtype-descriptor for the given fields.

— procedure: define-record-discloser rtype-descriptor discloser –> unspecific

Defines the method by which records of type rtype-descriptor are disclosed (see Writer). This is also exported by define-record-types and defrecord.

— procedure: define-record-resumer rtype-descriptor resumer –> unspecified

Sets rtype-descriptor's record resumer to be resumer. If resumer is #t (the default), records of this type require no particular reinitialization when found in dumped heap images; if resumer is #f, records of the type rtype-descriptor may not be dumped in heap images; finally, if it is a procedure, and the heap image is resumed with the usual image resumer, it is applied to each record whose record type descriptor is rtype-descriptor after the run-time system has been initialized and before the argument to usual-resumer is called.

The records-internal structure also exports these:

— record type: :record-type

The record type of record types.

— procedure: disclose-record record –> disclosed

This applies record's record type descriptor's discloser procedure to record to acquire a disclosed representation; see Writer.

For expository purposes, the record type record type might have been defined like so with Jonathan Rees's define-record-type macro:

     (define-record-type record-type :record-type
       (make-record-type name field-names)
       record-type?
       (name record-type-name)
       (field-names record-type-field-names))

or like so with Richard Kelsey's define-record-type macro:

     (define-record-type record-type
       (name field-names)
       ())

Of course, in reality, these definitions would have severe problems with circularity of definition.

4.7.4 Low-level record manipulation

Internally, records are represented very similarly to vectors, and as such have low-level operations on them similar to vectors, exported by the records structure. Records usually reserve the slot at index 0 for their record type descriptor.

Warning: The procedures described here can be very easily misused to horribly break abstractions. Use them very carefully, only in very limited & extreme circumstances!

— procedure: make-record length init –> record
— procedure: record elt ... –> record
— procedure: record? object –> boolean
— procedure: record-length record –> integer
— procedure: record-ref record index –> value
— procedure: record-set! record index object –> unspecified

Exact analogues of similarly named vector operation procedures.

— procedure: record-type record –> value

This returns the record type descriptor of record, i.e. the value of the slot at index 0 in record.