Next: , Previous: , Up: stdlib records   [Index]


5.6.4 Procedural layer

The procedural layer is provided by the (rnrs records procedural (6)) library.

Procedure: make-record-type-descriptor name parent uid sealed? opaque? fields

Return a record-type descriptor (RTD) representing a record type distinct from all built–in types and other record types.

The name argument must be a symbol. It names the record type, and is intended purely for informational purposes and may be used for printing by the underlying Scheme system.

The parent argument must be either #f or an RTD. If it is an RTD, the returned record type, t, extends the record type p represented by parent. An exception with condition type &assertion is raised if parent is sealed (see below).

The uid argument must be either #f or a symbol. If uid is a symbol, the record–creation operation is nongenerative i.e., a new record type is created only if no previous call to make-record-type-descriptor was made with the uid. If uid is #f, the record–creation operation is generative, i.e., a new record type is created even if a previous call to make-record-type-descriptor was made with the same arguments.

If make-record-type-descriptor is called twice with the same uid symbol, the parent arguments in the two calls must be eqv?, the fields arguments equal?, the sealed? arguments boolean–equivalent (both #f or both true), and the opaque? arguments boolean–equivalent. If these conditions are not met, an exception with condition type &assertion is raised when the second call occurs. If they are met, the second call returns, without creating a new record type, the same record–type descriptor (in the sense of eqv?) as the first call.

NOTE Users are encouraged to use symbol names constructed using the UUID namespace (for example, using the record–type name as a prefix) for the uid argument.

The sealed? flag must be a boolean. If true, the returned record type is sealed, i.e., it cannot be extended.

The opaque? flag must be a boolean. If true, the record type is opaque. If passed an instance of the record type, record? returns #f. Moreover, if ‘record-rtd’ is called with an instance of the record type, an exception with condition type &assertion is raised. The record type is also opaque if an opaque parent is supplied. If opaque? is #f and an opaque parent is not supplied, the record is not opaque.

The fields argument must be a vector of field specifiers. Each field specifier must be a list of the form (mutable name) or a list of the form (immutable name). Each name must be a symbol and names the corresponding field of the record type; the names need not be distinct. A field identified as mutable may be modified, whereas, when a program attempts to obtain a mutator for a field identified as immutable, an exception with condition type &assertion is raised. Where field order is relevant, e.g., for record construction and field access, the fields are considered to be ordered as specified, although no particular order is required for the actual representation of a record instance.

The specified fields are added to the parent fields, if any, to determine the complete set of fields of the returned record type. If fields is modified after make-record-type-descriptor has been called, the effect on the returned RTD is unspecified.

A generative record–type descriptor created by a call to make-record-type-descriptor is not eqv? to any record–type descriptor (generative or nongenerative) created by another call to make-record-type-descriptor. A generative record–type descriptor is eqv? only to itself, i.e., (eqv? rtd1 rtd2) if, and only if, (eq? rtd1 rtd2). Also, two nongenerative record–type descriptors are eqv? if, and only if, they were created by calls to make-record-type-descriptor with the same uid arguments.

Procedure: record-type-descriptor? obj

Return #t if the argument is a record–type descriptor, #f otherwise.

Procedure: make-record-constructor-descriptor rtd parent-constructor-descriptor protocol

Return a record-constructor descriptor (or constructor descriptor for short) that specifies a record constructor (or constructor for short), that can be used to construct record values of the type specified by rtd, and which can be obtained via record-constructor. A constructor descriptor can also be used to create other constructor descriptors for subtypes of its own record type. rtd must be a record–type descriptor. protocol must be a procedure or #f. If it is #f, a default protocol procedure is supplied.

If protocol is a procedure, it is handled analogously to the protocol expression in a define-record-type form.

If rtd is a base record type parent-constructor-descriptor must be #f. In this case, protocol is called by record-constructor with a single argument p. p is a procedure that expects one argument for every field of rtd and returns a record with the fields of rtd initialized to these arguments. The procedure returned by protocol should call p once with the number of arguments p expects and return the resulting record as shown in the simple example below:

(lambda (p)
  (lambda (v1 v2 v3)
    (p v1 v2 v3)))

Here, the call to p returns a record whose fields are initialized with the values of v1, v2, and v3. The expression above is equivalent to (lambda (p) p). Note that the procedure returned by protocol is otherwise unconstrained; specifically, it can take any number of arguments.

If rtd is an extension of another record type parent-rtd and protocol is a procedure, parent-constructor-descriptor must be a constructor descriptor of parent-rtd or #f. If parent-constructor-descriptor is a constructor descriptor, protocol is called by record-constructor with a single argument n, which is a procedure that accepts the same number of arguments as the constructor of parent-constructor-descriptor and returns a procedure p that, when called, constructs the record itself. The p procedure expects one argument for every field of rtd (not including parent fields) and returns a record with the fields of rtd initialized to these arguments, and the fields of parent-rtd and its parents initialized as specified by parent-constructor-descriptor.

The procedure returned by protocol should call n once with the number of arguments n expects, call the procedure p it returns once with the number of arguments p expects and return the resulting record. A simple protocol in this case might be written as follows:

(lambda (n)
  (lambda (v1 v2 v3 x1 x2 x3 x4)
    (let ((p (n v1 v2 v3)))
      (p x1 x2 x3 x4))))

This passes arguments v1, v2, v3 to n for parent-constructor-descriptor and calls p with x1, …, x4 to initialize the fields of rtd itself.

Thus, the constructor descriptors for a record type form a sequence of protocols parallel to the sequence of record–type parents. Each constructor descriptor in the chain determines the field values for the associated record type. Child record constructors need not know the number or contents of parent fields, only the number of arguments accepted by the parent constructor.

protocol may be #f, specifying a default constructor that accepts one argument for each field of rtd (including the fields of its parent type, if any). Specifically, if rtd is a base type, the default protocol procedure behaves as if it were (lambda (p) p). If rtd is an extension of another type, then parent-constructor-descriptor must be either #f or itself specify a default constructor, and the default protocol procedure behaves as if it were:

(lambda (n)
  (lambda (v1vj x1xk)
    (let ((p (n v1vj)))
      (p x1xk))))

The resulting constructor accepts one argument for each of the record type’s complete set of fields (including those of the parent record type, the parent’s parent record type, etc.) and returns a record with the fields initialized to those arguments, with the field values for the parent coming before those of the extension in the argument list. (In the example, j is the complete number of fields of the parent type, and $k$ is the number of fields of rtd itself.)

If rtd is an extension of another record type and parent-constructor-descriptor is #f, parent-constructor-descriptor is treated as if it were a constructor descriptor for the parent rtd of rtd with a default protocol.

Implementation responsibilities: If protocol is a procedure, the implementation must check the restrictions on it to the extent performed by applying it as described when the constructor is called. An implementation may check whether protocol is an appropriate argument before applying it.

(define rtd1
  (make-record-type-descriptor
   'rtd1 #f #f #f #f
   '#((immutable x1) (immutable x2))))

(define rtd2
  (make-record-type-descriptor
   'rtd2 rtd1 #f #f #f
   '#((immutable x3) (immutable x4))))

(define rtd3
  (make-record-type-descriptor
   'rtd3 rtd2 #f #f #f
   '#((immutable x5) (immutable x6))))

(define protocol1
  (lambda (p)
    (lambda (a b c)
      (p (+ a b) (+ b c)))))

(define protocol2
  (lambda (n)
    (lambda (a b c d e f)
      (let ((p (n a b c)))
        (p (+ d e) (+ e f))))))

(define protocol3
  (lambda (n)
    (lambda (a b c d e f g h i)
      (let ((p (n a b c d e f)))
        (p (+ g h) (+ h i))))))

(define cd1
  (make-record-constructor-descriptor
    rtd1 #f protocol1))

(define cd2
  (make-record-constructor-descriptor
    rtd2 cd1 protocol2))

(define cd3
  (make-record-constructor-descriptor
    rtd3 cd2 protocol3))

(define make-rtd1 (record-constructor cd1))

(define make-rtd2 (record-constructor cd2))

(define make-rtd3 (record-constructor cd3))

(make-rtd3 1 2 3 4 5 6 7 8 9)
⇒ #<record with fields initialized to 3, 5, 9, 11, 15, 17>
Procedure: record-constructor constructor-descriptor

Call the protocol of constructor-descriptor (as described for make-record-constructor-descriptor) and return the resulting constructor constructor for records of the record type associated with constructor-descriptor.

Procedure: record-predicate rtd

Return a procedure that, given an object obj, returns #t if obj is a record of the type represented by rtd, and #f otherwise.

Procedure: record-accessor rtd k

k must be a valid field index of rtd. The record-accessor procedure returns a one–argument procedure whose argument must be a record of the type represented by rtd. This procedure returns the value of the selected field of that record.

The field selected corresponds to the kth element (0–based) of the fields argument to the invocation of make-record-type-descriptor that created rtd. Note that k cannot be used to specify a field of any type rtd extends.

Procedure: record-mutator rtd k

k must be a valid field index of rtd. The record-mutator procedure returns a two–argument procedure whose arguments must be a record record r of the type represented by rtd and an object obj. This procedure stores obj within the field of r specified by k. The k argument is as in record-accessor. If k specifies an immutable field, an exception with condition type &assertion is raised. The mutator returns unspecified values.

(define :point
  (make-record-type-descriptor
    'point #f
    #f #f #f
    '#((mutable x) (mutable y))))

(define :point-cd
  (make-record-constructor-descriptor :point #f #f))

(define make-point (record-constructor :point-cd))

(define point? (record-predicate :point))
(define point-x (record-accessor :point 0))
(define point-y (record-accessor :point 1))
(define point-x-set! (record-mutator :point 0))
(define point-y-set! (record-mutator :point 1))

(define p1 (make-point 1 2))
(point? p1)                             ⇒ #t
(point-x p1)                            ⇒ 1
(point-y p1)                            ⇒ 2
(point-x-set! p1 5)                     ⇒ unspecified
(point-x p1)                            ⇒ 5

(define :point2
  (make-record-type-descriptor
    'point2 :point
    #f #f # f '#((mutable x) (mutable y))))

(define make-point2
  (record-constructor
    (make-record-constructor-descriptor :point2
      #f #f)))
(define point2? (record-predicate :point2))
(define point2-xx (record-accessor :point2 0))
(define point2-yy (record-accessor :point2 1))

(define p2 (make-point2 1 2 3 4))
(point? p2)                             ⇒ #t
(point-x p2)                            ⇒ 1
(point-y p2)                            ⇒ 2
(point2-xx p2)                          ⇒ 3
(point2-yy p2)                          ⇒ 4

(define :point-cd/abs
  (make-record-constructor-descriptor
   :point #f
   (lambda (new)
     (lambda (x y)
       (new (abs x) (abs y))))))

(define make-point/abs
  (record-constructor :point-cd/abs))

(point-x (make-point/abs -1 -2))        ⇒ 1
(point-y (make-point/abs -1 -2))        ⇒ 2

(define :cpoint
  (make-record-type-descriptor
   'cpoint :point
   #f #f #f
   '#((mutable rgb))))

(define make-cpoint
  (record-constructor
   (make-record-constructor-descriptor
    :cpoint :point-cd
    (lambda (p)
      (lambda (x y c)
	((p x y) (color->rgb c)))))))

(define make-cpoint/abs
  (record-constructor
   (make-record-constructor-descriptor
    :cpoint :point-cd/abs
    (lambda (p)
      (lambda (x y c)
	((p x y) (color->rgb c)))))))

(define cpoint-rgb
  (record-accessor :cpoint 0))

(define (color->rgb c)
  (cons 'rgb c))

(cpoint-rgb (make-cpoint -1 -3 'red))   ⇒ (rgb . red)
(point-x (make-cpoint -1 -3 'red))      ⇒ -1
(point-x (make-cpoint/abs -1 -3 'red))  ⇒ 1

Next: , Previous: , Up: stdlib records   [Index]