[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

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))

    How about

    (let loop ()
      (eval (list 'define-type <expr1> <expr2> <expr3>))

    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).


More information about the R6RS mailing list