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

compromise record-type proposal



Here is a new proposal based on the make-record-type proposal.  As
suggested by Bill, it defines both syntactic and procedural
interfaces.  The former is intended for ordinary
"abstraction-preserving" programming, while the latter is intended for
abstraction-breaking meta (environmental) programming, e.g., generic
data inspection and "surgery".

The abstraction-breaking facility permits the coding of
implementation-independent generic inspection facilities, but leaves
unspecified the consequences of mutating objects via the
abstraction-breaking facility.

The interfaces are designed to allow users and compilers to ignore the
possibility of field access or mutation via the abstraction-breaking
facility.  Although ignoring the possibility is neither safe nor
secure, the "blame" for any problems encountered via the use of the
abstraction breaking facility is placed squarely on code that breaks
the abstraction.

Plenty of bells and whistles could be (and possibly should be) added to
this structure, and slight variations could be made without altering
the basic structure.  Please resist, if you can, critiquing the
proposal based on such points at this stage.

Kent

--- start of define-record proposal ---

(define-record <base-id> <type-name> <field-id> ...)

<base-id> and each <field-id> must be identifiers.  <type-name> must
evaluate to a string.  define-record is a definition in the syntactic
sense and can appear anywhere and only where other definitions may
appear.  It evaluates <type-name> to produce a string s, creates a new
type t with associated type name s, and defines a set of variables as
follows:

     Variable:                  Value:

     make-<base-id>             record constructor
     <base-id>?                 type predicate
     <base-id>-<field-id>       field accessor (one per field-id)
     ...                       
     set-<base-id>-<field-id>!  field mutator (one per field-id)
     ...

Each of the values are procedures.  The record constructor constructs a
new record of type t.  The constructor accepts as many arguments as
there are fields; these arguments are the initial values of the fields
(in order).  The record predicate accepts one argument, an object.  It
returns #t if the object is of type t and #f otherwise.  A field
accessor accepts one argument, which must be of type t, and returns the
contents of the specified field of the argument.  A field mutator
accepts two arguments, the first of which must be of type t and the
second of which may be any object.  It places the second argument into
the specified field of the first argument.

[We could use a string constant corresponding to the printed
representation of <base-id> as the type name in place of the explicit
<type-name> argument, or do so only if <type-name> is not present.]


(record? <obj>)

Returns #t if <obj> is a record of any type and #f otherwise.  Note
that record? may be true of any Scheme value; of course, if it returns
#t for some particular value, then record-type-descriptor is applicable
to that value and returns an appropriate descriptor.


(record-type-descriptor <record>)

Returns a record-type descriptor representing the type of the given
record.  Operations on record type descriptors are described below.


(record-type-name <rtd>)

Returns the type name associated with the type represented by <rtd>.
The returned value is equal? to the type-name argument given in the
record definition that created the type represented by <rtd>.


(record-predicate <rtd>)

Returns a procedure for testing membership in the type represented by
<rtd>.  The returned procedure accepts exactly one argument and returns
#t if the argument is a member of the indicated record type; it returns
#f otherwise.


(record-type-field-names <rtd>)

Returns a list of unique strings identifying the fields in records of
the type represented by <rtd>.  Each element of the list corresponds to
a <field-id> in the record type definition.  The elements appear in no
particular order.

[Rationale: With field names as unique strings rather than symbols,
abstraction-breaking code cannot access fields by name.  With the list
of field names unordered, abstraction-breaking code cannot access
fields by position.  This does not prevent the coding of generic
inspectors and the like, but it does prevent programmers from building
in dependencies on field names or positional values in code not within
the scope of the record definition.]


(record-field-accessor <rtd> <field-name>)

Returns a procedure for accessing the value of a particular field of a
member of the type represented by <rtd>.

<field-name> must be a member (in the sense of eqv?/memv) of the list
of field names returned by (record-type-field-names <rtd>).

The returned procedure accepts one argument, which must be a record of
the appropriate type, and returns two values:  (1) a boolean flag that
is #f if the field has been found inaccessible (see below) and #t
otherwise; and (2) the current contents of the field, if the field has
not been found inaccessible (i.e., if the first value is #t), and
unspecified otherwise.

A field may be found inaccessible by an implementation if the
implementation can prove that the field cannot be accessed via the
field accessor defined by the record definition.

[Rationale: An implementation may optimize away a field from all
records of a type or may discard a field (or its contents) from a given
record at any time if it can prove that the field is inaccessible
except by way of accessors returned by record-field-accessor.  This
interface allows reliable use of accessors created by
record-field-accessor in the presence of such optimizations.  In a
sense, accessors created by record-field-accessor are analogous to weak
pointers: if the only access to a field is through such accessors, the
field or its contents may be discarded, although in this case no
guarantee is made that the field or contents will be discarded.]

Modifying a value obtained via a field accessor returned by
record-field-accessor has implementation-dependent consequences.  In
particular, an implementation is free to optimize code based on an
assumption that fields are accessed only via the accessors defined by
the record definition.


(record-field-mutator <rtd> <field-name>)

Returns a procedure for mutating the value of a particular field of a
member of the type represented by <rtd>.

<field-name> must be a member (in the sense of memv) of the list of
field names returned by (record-type-field-names <rtd>).

The returned procedure accepts two arguments.  The first must be a
record of the appropriate type.  The second may be any object.  One
value is returned, a boolean flag that is #f if the field has been
found inaccessible (see record-field-accessor) or immutable (see below)
and #t otherwise.  In the former case, no mutation occurs.

A field may be found immutable by an implementation if the
implementation can prove that the field cannot (or can no longer) be
mutated through the use of the field mutator defined by the record
definition.

Altering a field of a record via a field mutator returned by
record-field-mutator has implementation-dependent consequences.  In
particular, an implementation is free to optimize code based on an
assumption that fields are modified only via the mutators defined by
the record definition.

--- end of define-record proposal ---