[R6RS] revised draft of record srfi

dyb at cs.indiana.edu dyb
Wed Jul 27 22:11:37 EDT 2005

Thanks for your feedback, Marc.

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

I have no problem referring to the virtual entity as a type, but I prefer
to refer to the object that represents the type as a type descriptor,
since it does just that: describes the type.  The name make-type is
fine with me for the procedure that creates the type, but I would have
it return a type descriptor.  I'm also fine with just about any other
name, including the one in the SRFI draft.  I don't envision the
procedural interface being used directly by humans, so I don't think
the name matters much.

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

This is all consistent with calling the object that represents the type
a "type descriptor", which I prefer to "type object".

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

I'd be happy with keywords arguments for this procedure if and only
if we end up adding a keyword argument mechanism to the core language
(I'd also prefer we not add keyword arguments more generally, but that's
another issue.)  Again, however, I don't envision humans typing calls
to this procedure, so I don't think the interface matters overly much.

> 3) It would make sense for the type object (record type descriptor) to
>     be a record.  ...

I agree, and that's the way it is in Chez Scheme, but I feel that this
is probably going a step too far at this point.  If we can get the rest
of the specification done and still have time to deal with this issue,
that would be great.

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

You're right...this was an oversight.

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

I'd prefer we not make the field-specifier arguments a distinct type at
this point, especially if we aren't going to agree on a concise printed
representation for records, which seemed unlikely at the Boston meeting.
We can leave this open as an option for later; it's certainly an upward
compatible extension to the current proposal.

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

The rationale is that this procedure will often be used as the target
of a syntactic interface, and (symbolic) names may not be distinct in
the syntactic interface if different parts of a record definition
are produced by different (hygienic) macros.  Forcing the syntactic
interface to generate unique symbolic names is a hassle and may make
debugging harder.

Although we probably forgot to say it, the field names in the syntactic
interface must be distinct, in the same sense that let and lambda bindings
must be distinct, if accessor and mutator names are not specified.

(Another rationale is that one should not need to know the names of parent
record fields when defining a child record, and one would have to in order
to avoid conflicts.  On the other hand, I've pretty much decided that one
should have to use the parent type descriptor to get at parent fields,
although that's not how we have currently specified the interface.)

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

Good point---I agree.

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

Yes, we should nail this down.  Basically, if a uid is not specified,
two types created by different calls (statically or dynamically) to
make*type are distinct, two types created by make*type and define-type
are distinct, and two types created by two define-type forms appearing in
different parts of a program are distinct.  It is unspecified whether
evaluating the same define-type form twice produces distinct types
(because we couldn't agree on this in Boston).  If a uid is specified,
a call to make*type or use of define-type produces the same type as
another call to make*type or use of define-type iff they have the same
uid, and if they have the same uid, an error is signaled if they don't
have the same parent and the same field names in the same order with
the same mutability.  (I may be missing some constraints.)

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

We'll need to work on the text, but I'm not willing to say that a
different type is generated on each evaluation and others weren't
willing to say that the same type is generated on each evaluation,
so we need to leave this unspecified.  I don't see this as a bad
thing anyway; it's muth the same as

   (let ((f (lambda () '(a b c)))) (eq? (f) (f)))


   (let ((f (lambda () (lambda (x) x)))) (eq? (f) (f)))

being unspecified in r5rs (if I remember correctly).

> 9) The more I think about it, the more I feel that the record
>     initialization mechanism does not belong in the syntactic
>     interface.

Well, this could be a deal breaker, since I feel that it absolutely must
be in the syntactic interface for the reasons I outlined in my response
to your proposed record-definition syntax.

>     specified (in general different constructors may be needed).  Also
>     the "language" for specifying this constructor is limited and
>     crippled.

Yes, it is crippled.  I'm willing to make it more general, if others feel
that this is necessary, but I've found that this syntax covers nearly
every constructor I've ever wanted to write, and it's nice and simple.

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

Again, this is unacceptable to me for reasons I outlined in my response
to your proposed record-definition syntax.


More information about the R6RS mailing list