On Sep 3, 2009, at 9:30 AM, Derick Eddington wrote:
My wording in my last post below was hurried. Let me elaborate
better.
If you think "record type" is distinct from "record type descriptor",
and "record type definition" is only via define-record-type and not
via
make-record-type-descriptor, then some of the wording/design in R6RS
might be more clear to you.
Yes, I do see them as different things. [I'm just elaborating here
for the general benefit; you might know all of this already.]
When you do (define-record-type foo), foo is not bound as a variable
nor as a macro, which may seem strange since R6RS does not say what
it really is. There, foo is bound to a record type: an expand-time
bindings identifying foo as a record type, and providing the expander
with information about the record type, most importantly: it's rtd
and rcd. You can obtain these two using (record-type-descriptor foo)
and (record-constructor-descriptor foo). If there is ever a need to
attach any more information to foo, the implementation can easily
attach it without needing to expose it in a separate name.
So, (define-record-type foo) binds foo as a record type, while
(define foo ---) binds foo as a variable whose value might be an rtd
object.
But, the beginning of R6RS Libraries
Chapter 6 definitely gives the impression "record type" and "record
type descriptor" are the same.
I just reread the intro to Chapter 6 and did not get that
impression. :-) It's obvious to me that a record *type* and
an rtd *object* are two different things and live at different
times (but then, this is precisely what you're complaining
about).
Regardless of word-twisting, I think Will Clinger's main point is that
record types should always be record type descriptors so that it's not
necessary to determine whether a "record type" thing is a first-class
run-time descriptor or an expand-time handle. With R6RS, it's
necessary
to know if you need to use record-type-descriptor or not and whether
you
need to use parent-rtd or parent. I think the argument is that this
breaks the abstraction of "record type" in an unacceptable way.
[Probably you should not be talking on Clinger's behalf unless
you provide citation and quote what he says, but anyways ...]
I personally don't care about R6RS's procedural layer much. In
Ikarus before R6RS, it had simple records (now renamed structs to
avoid confusion) and they did have a syntactic and procedural
layers. The thing is: I never ever used the procedural layer in
all these years. (seems like I only added it for good measure)
So, there was never any confusion about what foo really is: it
can only be one thing: a record type; and the rtd object was
always hidden in the constructor, predicates, etc.
So, it might be that the source of confusion in R6RS is that,
again, two different groups of people had two different ideas
about what the record facility should look like, and being an
all-inclusive standard, we got both facilities. And while
both facilities work ok (i.e., can definitely be improved),
understanding that they're two separate things is not as
simple as understanding only one of them.
For example, my matcher matches records, and to support "record type"
things made with either define-record-type or
make-record-type-descriptor, I have to have special syntax to
distinguish whether the given record type is an expand-time handle or
evaluates to a run-time descriptor:
(define A (make-record-type-descriptor 'A #F #F #F #F '#()))
(match 1
((:record A) 'record)
(_ 'nope))
Unhandled exception
Condition components:
1. &who: record-type-descriptor
2. &message: "not a record type"
3. &syntax:
form: (record-type-descriptor A)
subform: #f
4. &trace: #<syntax (record-type-descriptor A)>
5. &trace: #<syntax (match-lambda ((:record A) 'record) (_ 'nope))>
6. &trace: #<syntax (match 1 ((:record A) 'record) (_ 'nope))>
(match 1
((:record (RTD A)) 'record)
(_ 'nope))
nope
Ideally, the match macro should ask the system about the binding
of A and extract some more information in order to at least do
more checking at expansion time instead of deferring it to
runtime (e.g., you should be able to check at expansion time if
"A" has the right number of fields listed in the match clause
instead of generating erroneous code that bombs at runtime).
If you insist that record types are ordinary variables bound to
values that may happen to hold rtds at run time, then you've just
given up on the ability to do anything with them at expansion
time.
I somewhat arbitrarily chose to make expand-time handles the default
and
automatically wrap them in record-type-descriptor in the match
expression expansion, because I assumed most users will use
define-record-type and I didn't want adding record-type-descriptor
to be
needed.
I think this is the correct assumption. As a matter of fact,
you can be safe ignoring the other layer completely. I cannot
believe anybody is using the procedural layer and making the
rtds manually and then wanting to use pattern matching (a
syntactic facility) to match on those.
But I could also justify making descriptor expressions the
default and require adding record-type-descriptor for expand-time
handles.
No you can't. :-)
Just in case it's not obvious, I do dislike the procedural
layer in R6RS and think it's too complicated (actually, the
whole R6RS records are too complicated). I can never put the
whole thing in my head at once. Like, I can never remember
what arguments make-record-type-descriptor takes and what
they should be, especially the vectors listing field names
(come on!), inheritance protocols, and how you cannot do
anything with a record without first creating a procedure
(predicate, accessor, mutator, etc) to do it for you.
Aziz,,,
[side remark: Ikarus's current implementation of R6RS records
sucks. Originally, I was going to adapt the same strategy
of implementing structs which would've made at least the
syntactic layer of R6RS records very robust and efficient.
Unfortunately, Will Clinger's formal comment that resulted
in adding "parent-rtd" to the syntactic layer made that
implementation strategy far more difficult. In the long
term, this should not be a problem since ikarus's syntactic
define-record-type now only expands to the procedural layer
and thus optimizing the procedural layer should automatically
benefit the syntactic layer, but this is an additional burden
on the implementor that (I believe) should not have been
there since the syntactic layer, by itself, is trivial to
optimize, while the procedural layer requires data-flow
analysis (across libraries to be most effective). As far as
I know, only Chez Scheme does some of that.]