9.25 gauche.record - Record types

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 Record types 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 predictate, 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.25.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.

It is portable. The API conforms two major record SRFIs,
SRFI-9 and SRFI-99, so the code using record types can run on
various Scheme systems.

It is efficient. Record types are less flexibile than classes,
but that allows Gauche to optimize more. Hence creating records and
accessing/modifying them are much faster than creating instances
of general classes and accessing/modifying them. It makes record
types preferable choice when you only need a mechanism to bundle
several related values to carry around, and don’t need fancier
mechanisms such as class redefinitions.

As Gauche’s extention, you can define pseudo record types,
which interprets ordinary aggregate types such as vectors and lists
as records. (For Common Lisp users; it is like the :type
option of defstruct).
This helps flexibility of interface. For example, you can ask
your library’s users to pass a point in a vector of three numbers,
instead of asking users to pack their point data into your
custom point record type. Yet inside your library you can
treat the passed data as if it is your point record type.
See Pseudo record types, for more details.

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.

The type-name identifier will be bound to a
record type descriptor, or rtd,
which can be used for introspection and reflection.
See Record types inspection layer and Record types 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 inheritance.

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.

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-namepred-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 first and the third forms define immutable fields, which can only
be intialized by the constructor but cannot be modified afterwards
(thus such fields don’t have modifiers).
The second and the fourth forms define multable 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.

9.25.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]
Returns #t iff obj is an instance of record type,
#f otherwise.

Function: record-rtdrecord

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

Function: rtd-namertd

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

Function: rtd-parentrtd

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

Function: rtd-field-namesrtd

[SRFI-99]
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-namesrtd

[SRFI-99]
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]
Returns #t iff the field with the name field-name
of a record represented by rtd is mutable.

9.25.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-rtdname field-specs :optional parent

[SRFI-99]
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]
Returns #t if obj is a record type descriptor,
#f otherwise.

Function: rtd-constructorrtd :optional field-specs

[SRFI-99]
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-predicatertd

[SRFI-99]
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-accessorrtd field-name

[SRFI-99]
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-mutatorrtd field-name

[SRFI-99]
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.25.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:

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: