For Development HEAD DRAFTSearch (procedure/syntax/module):

9.28 gauche.record - Record types

Module: gauche.record

This module provides a facility to define record types, user-defined aggregate types. The API is upper compatible to SRFI-9 (Defining Record Types) and SRFI-99 (ERR5RS Records).

Record types are implemented as Gauche’s classes, but have different characteristics from the general classes. See Introduction, for when you want to use record types.

The record API consists of three layers, following SRFI-99 and R6RS design.

The syntactic layer is the define-record-type macro that conveniently defines a record type and related procedures (a constructor, a predicate, accessors and modifiers) all at once declaratively. Knowing this macro alone is sufficient for most common usage of records.

The inspection layer defines common procedures to query information to the records and record types.

The procedural layer is a low-level machinery to implement the syntactic layer; you don’t usually need to use them in day-to-day programming, but they might be handy to create record types on-the-fly at runtime.


9.28.1 Introduction

Gauche provides a general way for users to define new types as new classes, using object system (see Object system), and indeed record types are implemented as Gauche’s classes. However, using record types instead of classes has several advantages.

The disadvantage of record types is that they don’t obey Gauche’s class redefinition protocol (see Class redefinition). That is, if you redefine a record with the same name, it creates a new record type unrelated to the old one. The record instances created from the old definition won’t be updated according to the new definition.

More importantly, record constructors, accessors and modifiers are tend to be inlined where they are used, to achieve better performance. Since they are inlined, the code that uses those procedures are not affected when the record type is redefined. This means if you redefine a record type, you have to reload (recompile) the sources that uses any of record constructors, accessors or modifiers.


9.28.2 Syntactic Layer

Macro: define-record-type type-spec ctor-spec pred-spec field-spec …

[R7RS base][SRFI-9][SRFI-99+] {gauche.record} Defines a record type, and optionally defines a constructor, a predicate, and field accessors and modifiers.

The type-spec argument names the record type, and optionally specifies the supertype (parent).

type-spec : type-name
                | (type-name parent option ...)

type-name : identifier
parent : expression
option ... : keyword-value list

The type-name identifier will be bound to a record type descriptor, or rtd, which can be used for introspection and reflection. See Inspection layer and Procedural layer for possible operations for record types. In Gauche, a record type descriptor is a <class> with a metaclass <record-meta>.

The parent expression should evaluate to a record type descriptor. If given, the defined record type inherits it; that is, all the slots defined in the parent type are available to the type-name as well, and the instance of type-name answers #t to the predicate of the parent type.

Since a record type is also a class, parent type is also a superclass of the defined record type. However, record types are limited to have single implementation inheritance. The parent type must be a subclass of <record>. (You can have abstract classes along the main inheritance, though. See mixins below.)

You can give a pseudo record base type as parent to define a pseudo record type, which allows you to access ordinary aggregates like vectors as records. See Pseudo record types for more details.

the option … part is Gauche’s extension. It must be a keyword-value list. The following keywords are recognized:

:mixins (class …)

Specifies auxiliary superclasses. The classes must be abstract, that is, must not have slots. It is to implement protocols in the record type, e.g. <sequence> (see gauche.sequence - Sequence framework).

:metaclass metaclass

Specifies alternative metaclass. By default, metaclass of record types is <record-meta>. If you specify an alternative metaclass, it must be a subclass of <record-meta>.

The ctor-spec defines the constructor of the record instance.

ctor-spec : #f | #t | ctor-name
          | (ctor-name field-name ...)

ctor-name : identifier
field-name : identifier

If it is #f, no constructor is created. If it is #t, a default constructor is created with a name make-type-name. If it is a single identifier ctor-name, a default constructor is created with the name. The default constructor takes as many arguments as the number of fields of the record, including inherited ones if any. When called, it allocates an instance of the record, and initialize its fields with the given arguments in the order (inherited fields comes first), and returns the record.

The last variation of ctor-spec creates a custom constructor with the name ctor-name. The custom constructor takes as many arguments as the given field-names, and initializes the named fields. If the inherited record type has a field of the same name as the ancestor record type, only the inherited ones are initialized. In Gauche, uninitialized fields remains unbound until some value is set to it.

The pred-spec defines the predicate of the record instance, which takes one argument and returns #t iff it is an instance of the defined record type or its descendants.

pred-spec : #f | #t | pred-name

pred-name : identifier

If it is #f, no predicate is created. If it is #t, a predicate is created with a name type-name?. If it is a single identifier, a predicate is created with the given name.

The rest of the arguments specify fields (slots) of the record.

field-spec
 : field-name   ; immutable, with default accessor
 | (field-name) ; mutable, with default accessor/modifier
 | (field-name accessor-name); immutable
 | (field-name accessor-name modifier-name); mutable

field-name    : identifier
accessor-name : identifier
modifier-name : identifier

The first and the third forms define immutable fields, which can only be initialized by the constructor but cannot be modified afterwards (thus such fields don’t have modifiers). The second and the fourth forms define mutable fields.

The third and fourth forms explicitly name the accessor and modifier. With the first and second forms, on the other hand, the accessor is named as type-name-field-name, and the modifier is named as type-name-field-name-set!.

Let’s see some examples. Here’s a definition of a record type point.

(define-record-type point #t #t
  x y z)

The variable point is bound to a record type descriptor, which is just a class. But you can take its class and see it is indeed an instance of <record-meta> metaclass.

point            ⇒ #<class point>
(class-of point) ⇒ #<class <record-meta>>

You can create an instance of point by the default constructor make-point. The predicate is given the default name point?, and you can access the fields of the created record by point-x etc.

(define p (make-point 1 2 3))

(point? p)  ⇒ #t
(point-x p) ⇒ 1
(point-y p) ⇒ 2
(point-z p) ⇒ 3

Since we defined all fields immutable, we cannot modify the instance p.

Here’s a mutable version of point, mpoint. You can modify its fields by modifier procedures and generalized set!.

(define-record-type mpoint #t #t
  (x) (y) (z))

(define p2 (make-mpoint 1 2 3)) ; create an instance

(mpoint-x p2)  ⇒ 1

(mpoint-x-set! p2 4)            ; default modifier
(mpoint-x p2)  ⇒ 4

(set! (mpoint-x p2) 6)          ; generalized set! also works
(mpoint-x p2)  ⇒ 6

Next one is an example of inheritance. Note that the default constructor takes arguments for fields of the parent record as well.

(define-record-type (qpoint mpoint) #t #t
  (w))

(define p3 (make-qpoint 1 2 3 4))

(qpoint? p3)  ⇒ #t      ; p3 is a qpoint
(mpoint? p3)  ⇒ #t      ; ... and also an mpoint

(mpoint-x p3) ⇒ 1       ; accessing inherited field
(mpoint-y p3) ⇒ 2
(mpoint-z p3) ⇒ 3
(qpoint-w p3) ⇒ 4

A small caveat: Accessors and modifiers for inherited fields (e.g. qpoint-x etc.) are not created.

Gauche’s convention is to enclose class name by <>. You can follow the convention and still explicitly gives simpler names (instead of make-<point> or <point>-x):

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

9.28.3 Inspection layer

This layer provides common procedures that operates on record type descriptors and record instances.

Note that a record type descriptor is a class in Gauche, so you can also use operators on classes (e.g. class-name, class-slots etc.) on record type descriptors as well. However, these procedures are more portable.

Function: record? obj

[SRFI-99][R6RS]{gauche.record} Returns #t iff obj is an instance of record type, #f otherwise.

Function: record-rtd record

[SRFI-99][R6RS]{gauche.record} Returns the record type descriptor of the record instance.

Function: rtd-name rtd

[SRFI-99]{gauche.record} Returns the name of the record type descriptor rtd.

Function: rtd-parent rtd

[SRFI-99]{gauche.record} Returns the parent type of the record type descriptor rtd. If rtd doesn’t have a parent, #f is returned.

Function: rtd-field-names rtd

[SRFI-99]{gauche.record} Returns a vector of symbols, each of which is the names of the direct fields of the record represented by rtd. The result doesn’t include inherited fields.

Function: rtd-all-field-names rtd

[SRFI-99]{gauche.record} Returns a vector of symbols, each of which is the names of the fields of the record represented by rtd. The result includes all inherited fields.

Function: rtd-field-mutable? rtd field-name

[SRFI-99]{gauche.record} Returns #t iff the field with the name field-name of a record represented by rtd is mutable.


9.28.4 Procedural layer

These procedures are low-level machinery on top of which define-record-type is implemented. They can be used to create a new record type at runtime.

Function: make-rtd name field-specs :optional parent

[SRFI-99]{gauche.record} Creates and returns a new record type descriptor with name name and having fields specified by field-specs. If parent is given, it must be a record type descriptor or #f. If it is a record type descriptor, the created record type inherits from it.

The field-specs argument must be a vector, each element of which is a field specifier. A field specifier can be a symbol, a list (mutable symbol), or a list (immutable symbol). The symbol names the field. A single symbol or (mutable symbol) format makes the field mutable, and (immutable symbol) format makes the field immutable.

Note: Gauche does not implement the extension suggested in SRFI-99 yet, which is sealed, opaque and uid arguments.

Function: rtd? obj

[SRFI-99]{gauche.record} Returns #t if obj is a record type descriptor, #f otherwise.

Function: rtd-constructor rtd :optional field-specs

[SRFI-99]{gauche.record} Returns a procedure that creates an instance record of the record type represented by rtd. Without field-specs, it returns the default constructor, which takes as many arguments as the number of fields of the record to initialize them.

You can give a vector of symbols as field-specs. The n-th symbol specifies which field of the instance should be initialized by the n-th argument. The field-specs vector cannot contain duplicate names. If the record type defines a field with the same name as the one in the parent record type, the custom constructor can only initialize the field of the derived type’s instance.

Function: rtd-predicate rtd

[SRFI-99]{gauche.record} Returns a predicate to test an object is an instance of rtd.

If rtd is a pseudo record type, the predicate merely tests the given object is in an appropriate type and has enough size to hold the contents. See Pseudo record types for the details.

Function: rtd-accessor rtd field-name

[SRFI-99]{gauche.record} Returns a procedure that takes one argument, an instance of rtd, and returns the value of the field-name of the instance.

An error is signaled if the record type doesn’t have the field of name field-name.

If rtd is inherits other record types, and it defines a field of the same name as inherited ones, then the accessor returned by this procedure retrieves the value of the field of the derived record.

Function: rtd-mutator rtd field-name

[SRFI-99]{gauche.record} Returns a procedure that takes two arguments, an instance of rtd and a value, and sets the latter as the value of the field-name of the instance.

An error is signaled if the record type doesn’t have the field of name field-name, or the named field is immutable.

Like rtd-accessor, if the record has a field with the same name as inherited one, the modifier returned by this procedure only modifies the field of the derived record.


9.28.5 Pseudo record types

A pseudo record type is a record type that does not create an instance of its own type. Instead it treats an object of other collection types, such as a vector, as if it had named fields. It’s easier to understand by an example:

(define-record-type (vpoint (pseudo-rtd <vector>)) #t #t
  (x) (y) (z))

(make-vpoint 1 2 3)  ⇒ #(1 2 3)
(vpoint-x '#(1 2 3)) ⇒ 1

(rlet1 v (make-vpoint 1 2 3)
  (set! (vpoint-y v) -1))
 ⇒ #(1 -1 3)

To create a pseudo record type, specify another pseudo record type as a parent. The procedure pseudo-rtd can be used to obtain a base pseudo record type of the suitable instance class.

Function: pseudo-rtd instance-class

{gauche.record} Returns a pseudo rtd suitable to use instance-class as a pseudo record.

Currently, <list>, <vector>, and uniform vector classes (<u8vector> etc.) are supported as instance-class.

The predicates of a pseudo record return #t if the given object can be interpreted as the pseudo record. In the above example of vpoint record, the predicate vpoint? returns #t iff the given object is a vector with 3 or more elements:

(vpoint? '#(0 0 0))   ⇒ #t
(vpoint? '#(0 0))     ⇒ #f
(vpoint? '(0 0 0))    ⇒ #f
(vpoint? '#(0 0 0 0)) ⇒ #t

We allow more elements so that the pseudo record can be used to interpret the header part of the longer data.



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT