[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Generative record types

|   Date: Wed, 24 Apr 1996 16:44:40 -0500 (EST)
|   From: "R. Kent Dybvig" <dyb@cs.indiana.edu>
|   Cc: rrrs-authors@martigny.ai.mit.edu
|   My objections to the make-record-type proposal are as follows.  As you
|   can see, they are largely addressed by your define-struct proposal.
|   1. I am in favor of opaque types, and object to the make-record-type
|   proposal on the grounds that it builds into the language support for
|   breaking abstractions.  If we had a mechanism for specifying
|   programming environment support, abstraction breaking mechanisms would
|   be worth considering, but not as part of the language.
|   One value of opacity is that it simplifies program analysis, both by
|   the user and by the compiler.  When programming, it's nice to be able
|   to scan the code where a record type declaration is visible and know
|   whether a field is used, whether it is assigned, and what type of value
|   it holds.  If any part of the program to which we pass the object can
|   obtain a field accessor or mutator, we've lost this ability.  Why do I
|   care?  I can give several reasons, but one simple one is that I like to
|   remove from records fields that are no longer needed, and I have a lot
|   more homework to do if I can't be sure someone isn't creating accessors
|   and/or mutators on the fly.  Why does the compiler care?  The compiler
|   might like to do type inference, which is made much more difficult if
|   we can't even determine where or whether field mutations occur.  The
|   run-time system may also care: Our storage management system, for
|   example, likes to segregate immutable objects from mutable ones for
|   performance reasons, and we can't do that unless we can prove that no
|   field mutators exist.

I understand and respect your objections, but perhaps we can arrive at
a compromise.

You raise several objections to transparent/translucent data

A. Inablity of a programmer to remove fields.
   This is similar to other situations about mismatched
   expectations/interfaces and changed representation.

   To a first approximation, how does this differ from changing the
   interface to a global/exported procedure?  You must do this on

   If you view the "abstraction breaking" abilities as "use at your
   own risk" hooks for debugging/pretty-printing/whatever,
   you as a programmer should be comfortable in removing these fields.  

   Anyone who "breaks the abstractions" will either do so by re-fetching
   the fields from the record descriptor (and thus their code will work)
   or by hard-coding something, in which case they will lose, but they
   were told that they could lose by doing it.

   In particular, consider a pretty printer.  The first time it meets
   a new record type, it will obtain a type descriptor and the field
   names, possibly cache this information, and then proceed to
   generate/customize code to print the objects of this type.
   This will be true for the context of one "session".  Re-defining
   the record-type on the fly will only generate a brand new type, so
   any cached information will not apply, and the pretty printer will
   operate correctly.

B. Type inference.  Why not add a type facility to the record

   For example, you could qualify each field with a predicate that the
   corresponding components must satisfy.  The mutators generated
   could then check and guarantee no inconsistencies.

   You might object because this introduces declarations, but the
   declarations need not be explicitly added by the user.  A type 
   inference engine powerful enough to deduce the types of the fields 
   should be able to qualify the fields itself, and remove the checks
   from the original mutators.

C. Unexpected mutation.  I have no problems with adding a mutability
   flag that compilers and runtime libraries can trust.
   The immutability of a field would have to be reported by the
   "abstraction breaking" facilities.  

   Given this ability a program could still ask for a mutator for an
   immutable field.  At this point I would allow implementation
   latitude (hence the language would declare this situation an error
   or specify that it is unspecified):
   - signal an error when the mutator is requested
   - produce a mutator that signals an error when invoked
   - produce a mutator that raises an exception when invoked
   - produce a mutator tha modifies the record potentially causing
     something else to fail later

|   2. I object to the procedural interface and prefer a syntactic
|   interface for two reasons.  First, the procedural interface is verbose,
|   so that portable code that uses it either will be ugly or will have to
|   carry with it the (possibly sizable) definition of a syntactic
|   extension.  Short snippets of expository code involving records either
|   won't be short or, if they employ a syntactic extension without
|   defining it, won't be well-specified.
|   Second, the procedural interface is difficult to compile efficiently
|   because it is too unstructured and too general.  The result will be
|   that each implementation that bothers to try to generate good code will
|   recognize only certain stylized uses (those produced by the local
|   define-record-type syntactic extension), so that code won't be portable
|   with any reasonable degree of efficiency.  By standardizing on a
|   syntactic construct, we stylize all code and introduce restrictions,
|   e.g., that all accessors and mutators be created together, that will
|   allow even simple compilers to generate good code.

I also prefer a syntactic interface most of the time.  However, a
syntactic interface is harder to use from a meta-program since it
involves invoking the compiler.

May I offer the suggestion of providing both?

MIT Scheme implements the record proposal, and a compatible

define-structure effectively makes record types, but the accessors,
constructors, and mutators (if any, since it has the option of
declaring a field immutable) are automatically defined as integrable
(inlineable) by the compiler.

Note that a proposal in the language would not have to introduce any
facility to inline into the language, merely mention in the
description that implementations are allowed to do it.

Note that under this scenario, the "abstraction breaking" code would
be the only code that would be compiled inefficiently (if any).
That's OK by me.  I only require a fast implementation for the
"natural and expected" customers of the structure definition.
For the rest, I require ability, not speed (although it is nice).

|   3. I prefer that we build in a way to specify immutability of fields,
|   although with a syntactic interface a good programmer or compiler can
|   determine immutability easily enough.

I would prefer to have a way of specifying it as well.
Again, a sufficiently smart compiler can add the declaration itself.