# LispKit Record

Library `(lispkit record)` implements extensible record types for LispKit which are compatible with R7RS and SRFI 131. A record provides a simple and flexible mechanism for building structures with named components wrapped in distinct types.

## Declarative API

`record-type` syntax is used to introduce new record types in a declarative fashion. Like other definitions, `record-type` can either appear at the outermost level or locally within a body. The values of a record type are called records and are aggregations of zero or more fields, each of which holds a single location. A predicate, a constructor, and field accessors and mutators are defined for each record type.

### Syntax

(define-record-type <type> <constr> <pred> <field> …) [syntax]

<type> defines the name of the new record type and potentially the supertype if the new type is an extension of an existing record type. Thus, it has one of the two possible forms, where <type name> is an identifier and <supertype> an arbitrary expression evaluating to a record type descriptor:

<type name>, or
(<type name> <supertype>).

The <constructor> is of one of the two possible forms:

<constructor name>, or
(<constructor name> <field name> …_)

When a constructor is of the form (<constructor name> <field name> …), the following holds:

- Each of the field names can be either a field name declared in the same `define-record-type` form, or any of its ancestors’ field names.
- If the record definition contains the same field name as one of its ancestors, it shadows the ancestor’s field name for the purposes of the constructor. The constructor’s argument initializes the child’s slot, and the ancestor’s slot of the same name is left uninitialized.
- It is an error if the same identifier appears more than once in the field names of the constructor spec.

<pred> is the identifier denoting the type predicate that recognizes instances of the new record type and its subtypes. Each <field> is either of the form:

(<field name> <accessor name>), or
(<field name> <accessor name> <modifier name>).

It is an error for the same identifier to occur more than once as a field name. It is also an error for the same identifier to occur more than once as an accessor or mutator name.

The `define-record-type` construct is generative: each use creates a new record type that is distinct from all existing types, including the predefined types and other record types - even record types of the same name or structure.

An instance of `define-record-type` is equivalent to the following definitions:

- <type name> is bound to a representation of the record type itself. If a <supertype> expression is specified, then it must evaluate to a record type descriptor that serves as the parent record type for the record type being defined.

- <constructor name> is bound to a procedure that takes as many arguments as there are <field name> elements in the (<constructor name> …) subexpression and returns a new record of type <name>. Fields whose names are listed with <constructor name> have the corresponding argument as their initial value. The initial values of all other fields are unspecified. It is an error for a field name to appear in <constructor> but not as a <field name>.

- <pred> is bound to a predicate that returns `#t` when given a value returned by the procedure bound to <constructor name> or any constructor of a subtype. It returns `#f` for everything else.

- Each <accessor name> is bound to a procedure that takes a record of type <name> and returns the current value of the corresponding field. It is an error to pass an accessor a value which is not a record of the appropriate type.

- Each <modifier name> is bound to a procedure that takes a record of type <name> and a value which becomes the new value of the corresponding field. It is an error to pass a modifier a first argument which is not a record of the appropriate type.

### Examples

The following record-type definition:

    (define-record-type <pare>
      (kons x y)
      pare?
      (x kar set-kar!)
      (y kdr))

defines `kons` to be a constructor, `kar` and `kdr` to be accessors, `set-kar!` to be a modifier, and `pare?` to be a type predicate for instances of `<pare>`.

    (pare? (kons 1 2))        ⇒  #t
    (pare? (cons 1 2))        ⇒  #f
    (kar (kons 1 2))          ⇒  1
    (kdr (kons 1 2))          ⇒  2
    (let ((k (kons 1 2)))
      (set-kar! k 3) (kar k)) ⇒  3

Below is another example showcasing how record types can be extended. First a new record type `<point>` is defined:

    (define-record-type <point>
      (make-point x y)
      point?
      (x point-x set-point-x!)
      (y point-y set-point-y!))

Next, a new record type `<color-point>` is defined to extend record type `<point>`:

    (define-record-type (<color-point> <point>)
      (make-color-point x y color)
      color-point?
      (color point-color))

The following transcript shows that `<color-point>` objects are also considered `<point>` objects which inherit all fields from `<point>`:

    (define cp (make-color-point 12 3 red))
    (color-point? cp)    ⇒  #t
    (point? cp)          ⇒  #t
    (color-point?
      (make-point 1 2))  ⇒  #f
    (point-x cp)         ⇒  12
    (set-point-x! cp 1)
    (point-x cp)         ⇒  1
    (point-color cp)     ⇒  #<color 1.0 0.0 0.0>

## Procedural API

Besides the syntactical `define-record-type` abstraction for defining record types in a declarative fashion, LispKit provides a low-level, procedural API for creating and instantiating records and record types. Record types are represented in form of record type descriptor objects which itself are records.

(record? obj) [procedure]

Returns `#t` if obj is a record of any type; returns `#f` otherwise.

(record-type? obj) [procedure]

Returns `#t` if obj is a record type descriptor; returns `#f` otherwise.

(record-type obj) [procedure]

Returns the record type descriptor for objects obj which are records; returns `#f` for all non-record values.

(make-record-type name fields) [procedure]
(make-record-type name fields parent)

Returns a record type descriptor which represents a new data type that is disjoint from all other types. name is a string which is only used for debugging purposes, such as the printed representation of a record of the new type. fields is a list of symbols naming the fields of a record of the new type. It is an error if the list contains duplicate symbols. parent refers to a record type descriptor for the supertype of the newly created record type. By default, parent is `#f`, i.e. the new record type does not have a parent type.

(record-type-name rtd) [procedure]

Returns the type name (a string) associated with the type represented by the record type descriptor rtd. The returned value is `eqv?` to the name argument given in the call to `make-record-type` that created the type represented by rtd.

(record-type-field-names rtd) [procedure]
(record-type-field-names rtd all?)

Returns a list of the symbols naming the fields in members of the type represented by the record type descriptor rtd. The returned value is `equal?` to the fields argument given in the call to `make-record-type` that created the type represented by rtd. If boolean argument all? is true (default is false), then a list of all symbols, also defined by parent types of rtd, is returned.

(record-type-parent rtd) [procedure]

Returns a record type descriptor for the parent type of the record type represented by the record type descriptor rtd. `record-type-parent` returns `#f` if rtd does not have a parent type.

(record-type-field-index rtd field) [procedure]
(record-type-field-index rtd fields)

Returns the zero-based index of the given field (a symbol) within the record type represented by record type descriptor rtd. If fields is a list of symbols, returns a list of corresponding indices. This procedure is useful for implementing efficient field access. It is an error if field is not a field name of the record type.

(record-type-tag rtd) [procedure]

Returns the type tag, i.e. an uninterned symbol, representing the type of records defined by rtd. The result of `record-type-tag` can be used together with procedure `type-of` of library `(lispkit type)`.

(make-record rtd) [procedure]

Returns an uninitialized instance of the record type for which rtd is the record type descriptor.

(record-ref record index) [procedure]
(record-ref record index rtd)

Returns the value of the field at zero-based index in record. If rtd (a record type descriptor) is provided, verifies that record is of the specified type. It is an error if index is out of bounds or if record is not of the appropriate type.

(record-set! record index value) [procedure]
(record-set! record index value rtd)
(record-set! record indices values)
(record-set! record indices values rtd)

Sets the field at zero-based index in record to value. If rtd (a record type descriptor) is provided, verifies that record is of the specified type. If indices is a list of indices and values is a list of values, sets multiple fields at once. It is an error if any index is out of bounds, if record is not of the appropriate type, or if record is immutable.

(record-constructor rtd fields) [procedure]

Returns a procedure for constructing new members of the type represented by the record type descriptor rtd. The returned procedure accepts exactly as many arguments as there are symbols in the given fields list; these are used, in order, as the initial values of those fields in a new record, which is returned by the constructor procedure. The values of any fields not named in fields are unspecified. It is an error if fields contain any duplicates or any symbols not in the fields list of the record type descriptor rtd.

(record-predicate rtd) [procedure]

Returns a procedure for testing membership in the type represented by the record type descriptor rtd. The returned procedure accepts exactly one argument and returns `#t` if the argument is a member of the indicated record type; it returns `#f` otherwise.

(record-field-accessor rtd field) [procedure]

Returns a procedure for reading the value of a particular field of a member of the type represented by the record type descriptor rtd. The returned procedure accepts exactly one argument which must be a record of the appropriate type; it returns the current value of the field named by the symbol field in that record. The symbol field must be a member of the list of field names in the call to `make-record-type` that created the type represented by rtd.

(record-field-mutator rtd field) [procedure]

Returns a procedure for writing the value of a particular field of a member of the type represented by the record type descriptor rtd. The returned procedure accepts exactly two arguments: first, a record of the appropriate type, and second, an arbitrary Scheme value; it modifies the field named by the symbol field in that record to contain the given value. The returned value of the modifier procedure is unspecified. The symbol field must be a member of the list of field names in the call to `make-record-type` that created the type represented by rtd.