[R6RS] revised draft of record srfi
Marc Feeley
feeley
Wed Jul 27 18:51:07 EDT 2005
On 22-Jul-05, at 5:08 PM, dyb at cs.indiana.edu wrote:
> I have attached a revised draft of the record srfi. The draft can
> also
> be found at:
>
> http://www-pu.informatik.uni-tuebingen.de/users/sperber/srfi/
> record-srfi.html
I'll have more to say about syntax, but in the interest of advancing
the discussion I will move on to other issues. These comments apply
to the revised record proposal.
1) The proposed syntactic form for record definition is now
"define-type". For consistency, the procedural interface should
also use the term "type", so instead of
"make-record-type-descriptor" it should simply be "make-type",
instead of "record-type-descriptor?" it should simply be "type?",
etc. Consequently the result of "make-type" is a "type" (we can
use the term "type object" to avoid confusion in certain contexts).
This terminology has been used by Gambit for some time and it works
out well, in particular in error messages, for example:
Gambit Version 4.0 beta 15
> (define-type point x y)
> (point-x 123)
*** ERROR IN (console)@2.1 -- (Argument 1) Instance of #<type #2
point> expected
(point-x 123)
The #<type #2 point> in the error messsage is the external
representation of the type object of the point type.
2) The procedure make-record-type-descriptor now takes 6 parameters.
The previous specification took fewer parameters and it is likely
that other parameters will be added in the future (either for R7RS
or for implementation dependent extensions, for example a "record
printer" parameter, a "record serializer" parameter, etc). Some of
the parameters have reasonable defaults (parent=#f, uid=#f). This
is crying out for optional named parameters, as explained in one of
my previous messages. With optional named parameters we could have
(define point2d
(make-record-type-descriptor 'point2d
'((mutable x) (mutable y))
sealed?: #f))
(define point3d
(make-record-type-descriptor 'point3d
'((mutable z))
parent: point2d))
assuming the name and fields parameters are required and in that
order and the other parameters are optional named parameters.
Named parameters would also be useful for the constructor of
records with many fields, where it is hard to remember the ordering
of the fields or some fields have a default value. For example:
(make-point3d z: 33
x: 11
y: 22)
I don't have a specific proposal for constructors (it all depends
on the features provided by the record definition system) but one
possibility would be to have all fields with an "init" form be
optional named parameters, and the other fields are required and
positional.
3) It would make sense for the type object (record type descriptor) to
be a record. I believe this will be the case anyway in most
implementations of the record system, so why not give its
definition as a "define-type"? Similarly each field could be
described using a record. This approach would standardize the
representation of records and make it easier for different
implementations of Scheme to exchange records. Note that
inheritance can be used by an implementation to extend the standard
representation with implementation specific information (such as
record attributes and field attributes). If the Scheme
implementations A and B use the representations RA and RB natively
for records (which are different extensions of the standard
representation), it is possible for them to exchange records in
useful ways: a record defined and created by A can be sent to B and
operated on by B using the R6RS record operations (i.e. some of the
features of RA are not accessible to B). Moreover, that record can
be sent back to A where now all the features of RA are accessible.
This is a very useful feature for distributed computing and for
"portability" of data between different implementations of Scheme
(which is just as important as portability of programs).
4) The specs for "make-record-type-descriptor" says that "An error is
signaled if parent is sealed (see below)." An error should also
be signaled if the parent is generative and uid is not #f (i.e.
a generative type can only be extended to give a generative type,
and a non-generative type can be extended to give either a
non-generative type or a generative type).
5) As explained above, "field specifiers" should be a distinct
type. This approach is more extensible than the concrete
representation using a two element list of the form
(mutable name) or (immutable name).
6) "the names [of fields] need not be distinct". What is the
rationale for this? It certainly is inconsistent with let
bindings, procedure parameter lists, etc.
7) I disagree that "Two records created by such a constructor are
equal according to equal? iff they are eq?." It is reasonable for
an implementation of Scheme to use records to implement a variety
of builtin types including pairs, vectors, strings, and the numeric
tower. The "equal? iff eq?" would preclude this. I propose that
the result of equal? be unspecified as soon as one of the arguments
is a record created by such a constructor, unless the two records
are eq?.
8) The concept of "type equality" is not well defined. When are two
types A and B equal ? This is not a simple matter since the types
can be defined using the procedural or syntactic interface. Can
the procedural interface create a type that is equal to a type
created by the syntactic interface? Are the initializers part of
the type? Is the ordering of the fields important? Are the sealed
and opaque flags important? How does the presence of uid affect
type equality?
In the syntactic interface section we find this passage:
The absence of a nongenerative clause implies that the defined
type is generative. In the latter case, a new type may be
generated once for each evaluation of the record definition or
once for all evaluations of the record definition, but the type
is guaranteed to be distinct even for verbatim copies of the same
record definition appearing in different parts of a program.
I don't understand the last part. What is "a verbatim copy of the
same record definition"? Is this a copy or not:
(let loop ()
(eval '(define-type point x y))
(loop))
How about
(let loop ()
(eval (list 'define-type <expr1> <expr2> <expr3>))
(loop))
where <expr1>, <expr2>, <expr3> are sometimes equal to 'point, 'x
and 'y respectively. What if you "load" a file twice? Does that
count as two evaluations of the same record definition (so that an
implementation can use the same type)?
I find this confusing. Why not simply say a different type
is generated on each evaluation? If you need the type to remain
the same over multiple evaluations, use a uid.
9) The more I think about it, the more I feel that the record
initialization mechanism does not belong in the syntactic
interface. For one thing it only allows a single constructor to be
specified (in general different constructors may be needed). Also
the "language" for specifying this constructor is limited and
crippled. For instance, the constructor cannot have a rest
parameter and the parent's constructor (and only that one) must
always be called and always before the other initializations.
Moreover it seems strange that the constructor obtained by the
procedural interface's "record-constructor" may be different from
the constructor generated by the syntactic interface. I propose
that the constructor generated by the syntactic interface be the
same as the procedural interface. More complex constructors can be
defined separately as "normal" procedures, where all the expressive
power of Scheme is available (rest parameters, implementation
specific extensions, etc).
Marc
More information about the R6RS
mailing list