I am reviewing some code in Vicare’s expander to clean up the internal
representation of r6rs record types and related syntaxes (is-a?
,
slot-ref
, slot-set!
, …). All the changes discussed here are
in the master branch.
Whenever we use the syntax define-record-type
, a new syntactic binding is
created using the record–type name as name of the syntactic identifier; its
descriptor is a pair with format:
($record-type-name . ?value)
where the symbol $record-type-name
is the “type” of the descriptor and
?value is its “value”.
Up until now, the ?value in the descriptor has been a list with the first two items being: the syntactic identifier bound to the record–type descriptor (rtd), the syntactic identifier bound to the default record–constructor descriptor (rcd); optionally another item can be present in the list, a record carrying further informations about the record–type.
I changed this and now the ?value is itself a record of type
<r6rs-record-type-spec>
; this way the syntactic binding descriptor can carry
informations in a more ordered fashion. This should open the door to a better
handling of types in the expander.
I have added the clause destructor-protocol
to the body of
define-record-type
; destructor-protocol
is a new syntactic binding
exported by the library (vicare)
. The syntax define-record-type
accepts this clause only when the selected language is non–strict r6rs.
The definition clause:
(destructor-protocol ?expr)
allows the specification of an expression ?expr which must evaluate to the destructor protocol function; this function is used as explained below to construct a destructor function to be called:
delete
syntax.
It is possible for a destructor function to be applied multiple times to the same record: once a destructor is set in the descriptor, it can be explicitly applied to records and later applied again by the garbage collector. Destructor functions must be written in such a way that multiple applications are not a problem. For example, it is usually possible, upon destruction, to reset some record fields to the void object: when the destructor detects a field set to void, it knows that the record has already been finalised.
Here is how the destructor function is built:
destructor-protocol
clause and no parent: ?expr
must evaluate to a closure object accepting no arguments and returning a closure
object acting as destructor function for records of this type. For example:
(define-record-type <port> (fields port) (destructor-protocol (lambda () (lambda (record) (close-port (<port>-port record))))))
destructor-protocol
clause and a parent specified with
the parent
or parent-rtd
clauses: ?expr must evaluate to a
closure object accepting as single argument the parent’s destructor function and
returning as single value a closure object acting as destructor function for records
of this type. For example:
(define-record-type <port> (fields port) (destructor-protocol (lambda () (lambda (record) (close-port (<port>-port record)))))) (define-record-type <file> (parent <port>) (fields filename) (destructor-protocol (lambda (destroy-<port>) (lambda (record) (destroy-<port> record)))))
notice that the destructor of ‘<file>’ is meant to call the destructor of its supertype ‘<port>’ at some point; however calling the supertype’s destructor is optional.
destructor-protocol
clause, it has a parent and the
parent has a destructor function: the parent’s destructor function becomes this
type’s destructor function.
How many “record” objects are there in Vicare?
Lightweight records providing: basic type disjunction (all the other records are in truth structs), simple “fields initialising” constructors, access to named slots, no inheritance, custom object printers, instance destructors.
Heavyweight records providing: type disjunction, access to named slots, simple inheritance, multiple construction protocols, custom object printers (as extension to r6rs), instance destructors (as extension to r6rs).
Heavyweight records implemented by the library (nausicaa)
providing: type
disjunction, access to named slots, simple inheritance, multiple construction
protocols, custom object printers, instance destructors, class methods, support for
mixins, abstract types (class types whose public constructor raises an exception).
In addition to these both the tagged language half–written in the library
(vicare)
and the Nausicaa language implemented by the library
(nausicaa)
implement object types representing the “object–oriented”
specification of built–in Scheme objects like fixnums, vectors, and others.
Are there too many of these? I am tempted to reorganise everything and:
(vicare structs)
, rather than by (vicare)
.
define-record-type
as the only public record
facility.
define-class
and integrate all its features in
define-record-type
.
That would be a major backwards–incompatible change. I will have to think more
about this… in the meantime I will continue developing
define-record-type
trying to understand how much it can be extended without
making the expander a huge mess.