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 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.
• Record types introduction: | ||
• Record types syntactic layer: | ||
• Record types inspection layer: | ||
• Record types procedural layer: | ||
• Pseudo record types: |
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.
: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.
[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
metaclassSpecifies 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))
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.
[SRFI-99][R6RS]{gauche.record
}
Returns #t
iff obj is an instance of record type,
#f
otherwise.
[SRFI-99][R6RS]{gauche.record
}
Returns the record type descriptor of the record instance.
[SRFI-99]{gauche.record
}
Returns the name of the record type descriptor 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.
[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.
[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.
[SRFI-99]{gauche.record
}
Returns #t
iff the field with the name field-name
of a record represented by rtd is mutable.
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.
[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.
[SRFI-99]{gauche.record
}
Returns #t
if obj is a record type descriptor,
#f
otherwise.
[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.
[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.
[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.
[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.
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.
{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.