perm filename RECORD.PUB[1,LMM] blob sn#113648 filedate 1974-07-27 generic text, type T, neo UTF8
.require "HEADER.PUB" source!file;
.KEEP 15;
{INDENT 0,0;⎇↓_23.11__The_Record_Package_↓
{INDEXX(|BEGIN record package (in clisp)|)⎇{XNOTE; SEND FOOT ⊂
{L!53:⎇{INDENT 0,4;⎇{!NOTE!⎇ \The record package was written by L. M. Masinter.
.SKIP 2;
.SKIP 3;
The advantages of "data-less" or data-structure-independent
programming have long been known: more readable code, fewer bugs, the
ability to change the data structure without having to make major
modifications to the program, etc. The record package in CLISP both
encourages and facilitates this good programming practice by
providing a uniform syntax for accessing and storing data into many
different types of data structures, e.g. those employing arrays, list
structures, atom property lists, hash links, etc., or any combination
thereof, as well as removing from the user the task of writing the
various access and storage routines themselves. The user declares
(once) the data structure(s) used by his programs, and thereafter
indicates the manipulations of the data in a
data-structure-independent manner. The record package automatically
computes from the declaration(s) the corresponding INTERLISP
expressions necessary to accomplish the indicated access/storage
operations. The user can change his data structure simply by changing
the corresponding declaration(s), and his program automatically
(re)adjusts itself to the new conventions.
.SKIP 3;
The user informs the record package about the format of his
data structure by making a record declaration.
A record declaration defines a ↓_record_↓, i.e. a data structure.
(Note that the record itself is an abstraction
that exists only in the user's head.) The record declaration
is essentially a template which describes the record,
associating names with its various parts or ~2fields~1.
For example, the record declaration
~3(RECORDα MSGα (IDα (FROMα TO)α .α TEXT))~1
describes a data structure called ~3MSG~1, which contains four
fields: ~3ID, FROM, TO,~1 and ~3TEXT.~1 The user can then reference
these fields by name, either to retrieve their contents, or to store
new data into them, by using the : operator followed by the field
For example, for the above record declaration,
~3X:FROM~1 would be equivalent (and translate) to (~3CAADRα X)~1,
and ~3Y:TO←Z~1 to ~3(RPLACAα (CDADRα Y)α Z)~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \or ~3/RPLACA~1 or ~3FRPLACA~1, depending on
the CLISP declaration in effect.
.SKIP 2;
The fields of a record can be further broken down into subfields by additional
declarations within the record, e.g.
.SKIP 1;
would permit the user to refer to ~3TEXT~1, or to its subfields
~3HEADER~1 and ~3TXT~1.
.SKIP 3;
Note that what the record declaration is really
doing is specifying the
~2dataα-paths~1{INDEXX(|data-paths (in records in clisp)|)⎇
of the structure, and thereby specifying how the
corresponding access/storage operations are to be carried out.
For example,
says the ~3HEADER~1 of a ~3MSG~1 is to be found as the first element
of its ~3TEXT~1, which is the second tail of the ~3MSG~1 itself.
Hence, ~3X:HEADER←string~1 is achieved by performing
~3(RPLACAα (CDDRα X)α string)~1.
.SKIP 3;
Note also that when the user writes ~3X:HEADER~1, he is
implicitly saying the ~3X~1 is an instance of the
record ~3MSG~1, or at least is to be treated as
such for this particular operation. In other words,
the interpretation of ~3X:FORM~1
~2↓_never depends on the value of X_↓~1.
The record package (currently) does not provide any facility which
uses ~2run-time~1 checks to determine data paths,
nor is there any
error checking other than that provided by INTERLISP itself.
For example, if ~3X~1 happened to be an array, ~3X:HEADER~1
would still compute ~3(CAADRα X)~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \However, it is possible
to make the interpretation of ~3X:HEADER~1 differ from
that of ~3Y:HEADER~1 (regardless of the values of
~3X~1 and ~3Y~1), by using local record
declarations, as described on {PAGEREF L!41⎇.
Note that this distinction depends on a ~2translationα-~1time check, not run-time.
.SKIP 3;
The user may also elaborate a field by declaring that field name
in a ~2separate~1 record declaration (as opposed to an embedded
declaration). For example, te two declarations
~3(RECORD (MSG (FROM TO) . TEXT))~1 and
~3(RECORD TEXT (HEADER . TXT))~1 subdivides TEXT into two
subfields. In this case, the interpretation of X:HEADER is
that X is an instance of TEXT; however the user may specify
X:MSG.HEADER to achieve the interpretation "X is a MSG, retrieve
its HEADER". In general, the user may specify a chain of such
data paths X:R.F1.F2.F3; only as much of the path as is necessary
to disambiguate the access path need be specified. 
.SKIP 2;
.SKIP 3;
~3RECORD~1 (used to specify elements and tails of a list structure)
is just one of several recordα-types currently implemented.
For example, the user can specify 'optional' fields, i.e. property list format,
by using the record type ~3PROPRECORD~1, or fields to be associated
with parts of the data structure via hash links, by using the recordα-type
~3HASHRECORD~1, or that a new data type be used to store multiple fields, by using
the record-type DATATYPE,
 or even specify the access definitions in the
record declaration himself, by using the recordα-type ~3ACCESSFN~1.
These are described in detail below.
.SKIP 3;
The record package also provides a facility for ~2creating~1
new data structures using a record declaration as a guide or template.
Initial values for the various fields
can be specified in the ~3CREATE~1 expression,{INDEXX(|CREATE (record package)|)⎇
or defaulted to values specified in the record declaration itself.
Alternatively, ~3CREATE~1 can be instructed to
use an existing datum as a model, i.e. to
obtain the field values for the new datum from the corresponding
fields of the existing datum, or even to actually re-use the
structure of the existing datum itself.
.SKIP 3;
The record package also provides for some types of records a 
facility for testing a data structure to determine its type,
via a TYPE? expression.
This facility is primarily intended for DATATYPE and TYPERECORD
structures, although the user may declare how ~3TYPE?~1 expressions
are to be interpreted for other record types.
.SKIP 3;
As with all DWIM/CLISP facilities, the record package contains
many doα-whatα-Iα-mean features, spelling correction
on field names, record types, etc.
In addition, the record package includes
a ~3RECORDS~1 ↓_prettydef_↓ macro{INDEXX(|RECORDS (prettydef macro)|)⎇
for dumping record declarations, as well as the appropriate
modifications to the file package (Section 14), so that
↓_files?_↓ and ↓_cleanup_↓ will inform the user about
records that need to be dumped.
.SKIP 4;
.KEEP 12;
↓_Record_Declarations_↓{INDEXX(|BEGIN record declarations (in clisp)|)⎇
.SKIP 3;
{INDENT 0,0;⎇A record declaration is an expression of the form
.SKIP 1;
(recordα-typeα record-nameα fieldsα .α α{defaults and/or subfieldsα⎇)
.SKIP 1;
This expression is evaluated to effect the corresponding
declaration.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \Local record
declarations are performed by including an expression of this form
in the CLISP declaration for that function
({PAGEREF L!41⎇), rather than evaluating the expression itself.
.SKIP 2;
.SKIP 3;
{INDENT 0,3;⎇1. ↓_recordα-type_↓ specifies the "type" of data being
described by the record declaration, and thereby implicitly specifies
the data paths, i.e. how the corresponding access/storage operations
are performed.
↓_recordα-type_↓ currently is either ~3RECORD, TYPERECORD,
~3RECORD~1 and ~3TYPERECORD~1 are used to describe list structures,
~3ARRAYRECORD~1 to describe arrays, ~3ATOMRECORD~1 to describe 
(the property list of) atoms, and ~3PROPRECORD~1 to describe lists
that use property list format. ~3HASHRECORD~1 can be used with any
type of data: since it simply specifies the
data path to be a hashα-link. ~3ACCESSFN~1 is also type-less;
the user specifies the dataα-path(s) in the record declaration
itself, as described below.
.SKIP 3;
{INDENT 0,3;⎇2. ↓_recordα-name_↓ is a literal atom used to identify
the record declaration for dumping to files via the ~3RECORDS~1
↓_prettydef_↓ macro,{INDEXX(|RECORDS (prettydef macro)|)⎇ and for
creating instances of the record via ~3CREATE~1.
(record package)|)⎇
For most top-level declarations, ↓_recordα-name_↓
is optional, e.g. ~3(RECORDα (IDα (FROMα TO)α .α TEXT))~1 is
perfectly acceptable.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \If ↓_recordα-name_↓ is omitted, it simply means that
the user cannot specify the record by name, e.g. when calling
~3CREATE~1, or when using the ~3RECORDS~1 ↓_prettydef_↓ command.
.SKIP 2;
.SKIP 3;
↓_recordα-name_↓ is obligatory and is used as an
indicator in ~3CAR~1 of the datum to signify what "type" of record it
~3CREATE~1 will insert an extra field containing ↓_recordα-name_↓ at
the beginning of the structure, and the translation of the access and
storage functions will take this extra field into account.{XNOTE;SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \Note: this type-field is used in the translations of TYPE? expressions.
.SKIP 2;
.SKIP 3;
{INDENT 3,3;⎇For subfield declarations, ↓_recordα-name_↓ is also
obligatory, and specifies the parent field that is being elaborated,
as described below.
.SKIP 3;
{INDENT 0,3;⎇3. ↓_fields_↓ describes the structure of the record. Its
exact interpretation varies with the ↓_recordα-type_↓:
.SKIP 3;
{INDEXX(|RECORD (record package)|)⎇{INDENT 10,10;⎇For ~3RECORD~1,
↓_fields_↓ is a list whose non-~3NIL~1 literal atoms are taken as
field-names to be associated with the corresponding elements and
tails of a list structure. ~3NIL~1 can be used as a place marker to
fill an unnamed field, e.g. ~3(Aα NILα B)~1 describes a three element
list, with ~3B~1 corresponding to the third element.
.SKIP 3;
{INDEXX(|TYPERECORD (record package)|)⎇{INDENT 10,10;⎇For
~3TYPERECORD~1, ↓_fields_↓ has the same meaning as for ~3RECORD~1.
However, since ~3CAR~1 of the datum contains an indicator signifying
its "type," the translation of the access/storage functions differ
from those of ~3RECORD~1. For example, for
X:FROM~1 translates as ~3(CAADDRα X)~1, not
~3(CAADRα X)~1.
.SKIP 3;
{INDENT 10,10;⎇{INDEXX(|ATOMRECORD (record package)|)⎇For ~3ATOMRECORD~1,
↓_fields_↓ is a list of property names, e.g.
Accessing will be performed with ↓_getp_↓, storing with ↓_put_↓.
.SKIP 3;
{INDENT 10,10;⎇{INDEXX(|PROPRECORD (record package)|)⎇For
~3PROPRECORD~1, ↓_fields_↓ is also a list of property names.
Accessing is performed with ↓_get_↓, storing with ↓_putl_↓.
SEND FOOT ⊂ {INDENT 0,4;⎇{!NOTE!⎇ \A new function (part of the record
package), similar to ↓_put_↓, which takes a list as its first
argument, searches the list looking for an occurrence of the given
property name (its second argument). If found, it replaces the next
element with the new property value (its third argument), otherwise
adds the property name and property value to the list.
.SKIP 2;
For example,
.SKIP 1;
could be used to describe an entry on the history list
(see Section 22).{XNOTE; SEND FOOT ⊂
{INDENT 0,4⎇{!NOTE!⎇ \Note that ~3(ATOMRECORDα (FOOα FIE)))~1
is equivalent to ~3(RECORD (VALUE . PROPS)
the difference being in the translations.
In the first case, ~3X:FIE~1 translates as ~3(GETPα Xα (QUOTEα FIE)),~1,
in the second case, as ~3(GETα (CDRα X)α (QUOTEα FIE))~1.
Note also that in the first case, if ~3X~1
is not a literal atom, INTERLISP
(i.e. ↓_getp_↓) will generate an error.
.SKIP 2;
.SKIP 3;
{INDENT 10,10;⎇{INDEXX(|HASHRECORD (record package)|)⎇For
~3HASHRECORD~1 (or ~3HASHLINK~1), ↓_fields_↓ is usually just
↓_fieldα-name_↓, i.e. an atom, and is the name by which the
corresponding hashα-value is referred to. For example,
for ~3(RECORDα (Aα Bα .α C)α (HASHRECORDα Bα FOO)),
X:FOO~1 translates as ~3(GETHASHα (CADRα X)).~1
If ↓_fieldα-name_↓ is a list,
it is interpreted as (fieldα-nameα arraynameα arraysize).
In this case, ↓_arrayname_↓ indicates the hashα-array to be used.
For example, ~3(HASHRECORDα (CLISPα CLISPARRAY)) would permit
the user to obtain the CLISP translation of ~3X~1 by simply writing ~3X:CLISP~1.
↓_arraysize_↓ is used for initializing the hash array:
if ↓_arrayname_↓ has not been initialized
at the time of the declaration, it will be set to
~3(HARRAYα (ORα arraysizeα 100))~1.
.SKIP 3;
{INDENT 10,10;⎇For ~3ARRAYRECORD~1{INDEXX(|ARRAYRECORD (record package)|)⎇,
↓_fields_↓ is a list of fieldα-names that are associated
with the corresponding elements of the array.
~3NIL~1 can be used as a place marker for an unnamed
field (element). Positive integers can be used
as abbreviation for the corresponding number of
~3NIL~1s. For example,
describes an eight element array, with ~3ORG~1 corresponding to the
first element,  ~3ID~1 to the fourth, and ~3TEXT~1 to the eighth.
.SKIP 3;
{INDENT 10,10;⎇For ~3ACCESSFN~1{INDEXX(|ACCESSFN (record package)|)⎇
(or ~3ACCESSFNS~1), ↓_fields_↓ is a list of the form (fieldα-nameα
accessdefinitionα setdefinition), or a list of elements of this form.
↓_accessdefinition_↓ is a function of one argument, the datum, and
will be used for accessing. ↓_setdefinition_↓ is a function of two
arguments, the datum and the new value,  and is used for
storing.{XNOTE; SEND FOOT ⊂ {INDENT 0,4;⎇{!NOTE!⎇ \Currently, an
error is generated if ~3CREATE~1 is called with a record declaration
containing an ~3ACCESSFNS~1 record-type.
.SKIP 2;
For example, ~3(HASHRECORDα FOO)~1 and
~3(ACCESSFNα (FOOα GETHASHα PUTHASH))~1 are equivalent:
in both cases, ~3X:FOO~1 translates as ~3(GETHASHα FOO)~1.
Similarly, ~3(ACCESSFNα (DEFα GETDα PUTD))~1 would
permit defining functions by writing fn:~3DEF~1←definition.{XNOTE; SEND FOOT ⊂
(DEFINEα (LISTα (LISTα FNα DEF]~1 would be preferable to using ↓_putd_↓.
.SKIP 2;
.SKIP 3;
.KEEP 7;
{INDENT 0,3;⎇4. α{defaults and/or subfieldsα⎇ is optional. It may contain
expressions of the form:
.SKIP 3;
{INDENT 10,10;⎇(1) fieldα-nameα ←α form - specifies the default value
for ↓_fieldα-name_↓. Used by ~3CREATE~1.
.SKIP 3;
(2) ~3DEFAULT~1α ←α form - specifies default value for every field
not given a specific default via (1).
.SKIP 3;
(3) a subfield declaration - i.e. a record declaration of any of the
above types. For subfield declarations, ↓_recordα-name_↓ is
obligatory. Instead of identifying the declaration as with the case
of top level declarations, ↓_recordα-name_↓ identifies the parent
field or record that is being described by the subfield declaration.
It must be either the recordα-name of the immediately superior
declaration, or one of its fieldα-names (or else an error is
.SKIP 3;
Subfields can be nested to an arbitrary depth.
.SKIP 3;
Note that in some cases, it makes sense for a given field to have more than
one subfield declaration. For example, in
~3B~1 is elaborated by both a ~3PROPRECORD~1 and ~3HASHRECORD~1.
Similarly, ~3(RECORDα (Aα B) (RECORDα Aα (Cα D))
(RECORDα Aα (FOOα FIE)))~1 is also acceptable,
and essentially "overlays" ~3(FOOα FIE)~1 and ~3(Cα D)~1, i.e.
~3X:FOO~1 and ~3X:C~1 would be equivalent. In such cases, the
~2first~1 subfield declaration is the one used by ~3CREATE~1, e.g.
.SKIP 1;
~3(RECORDα Xα (Aα B)α α 
(RECORDα Aα (Cα D))α α (RECORDα Aα (FOOα FIEα FUM))α )~1
will cause ~3(CREATEα X)~1 to construct
~3((NILα NIL)α NIL)~1, not ~3((NILα NILα NIL)α NIL)~1,
as would be the case if the subfield declaration
~3(RECORDα Aα (Cα D))~1 were removed.
{INDEXX(|END record declarations (in clisp)|)⎇
.SKIP 4;
{INDENT 0,0⎇↓_CREATE_↓{INDEXX(|BEGIN CREATE (record package)|)⎇
.SKIP 3;
Record operations can be applied to arbitrary structures,
i.e. structures created directly by user programs can be
manipulated in a dataα-independent manner using record declarations.
However, to be completely dataα-independent,
new data should be created using the same
declarations that define its data paths. This can be done by means of
an expression of the form
~3(CREATEα recordα-nameα .α α{assignmentsα⎇)~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \~3CREATE~1 is
not defined as a function. Instead, DWIM calls
the appropriate function in the record package giving
it the entire ~3CREATE~1 expression as an argument.
The translation of the ~3CREATE~1 expression, i.e. the
INTERLISP form which is evaluated to construct the datum,
is then stored elsewhere, as with iterative statements and pattern matches.
.SKIP 2;
α{assignmentsα⎇ is optional and may contain expressions of the following form:
.SKIP 3;
{INDENT 5,34;⎇(1) fieldα-nameα ←α form∂(35)specifies initial value
for ↓_fieldα-name_↓.
.SKIP 3;
{INDENT 5,34;⎇(2) ~3USING~1{INDEXX(|USING (record package)|)⎇α
form∂(35)specifies that for all fields not given a value by (1), the
value of the corresponding field in ↓_form_↓ is to be used.
.SKIP 3;
(3) ~3COPYING~1{INDEXX(|COPYING (record package)|)⎇ form∂(35)like
~3USING~1 except the corresponding values are copied (↓_copy_↓).
.SKIP 3;
(4) ~3REUSING~1{INDEXX(|REUSING (record package)|)⎇ form∂(35)like
~3USING~1, except that wherever possible, the corresponding
~2structure~1 in ↓_form_↓ is used (similar to operation of
↓_subpair_↓ and ↓_sublis_↓).
.SKIP 3;
{INDENT 0,0;⎇For example, following ~3(RECORDα FOOα (Aα Bα C))~1,
.SKIP 1;
~3(CREATEα FOOα A←Tα USINGα X)~1 translates as ~3(LIST T (CADR X) (CADDR X)),~1
.SKIP 1;
as ~3(LIST T (COPY (CADR X)) (COPY (CADDR X))),~1 and
.SKIP 1;
~3(CREATEα FOOα A←Tα REUSINGα X)~1 as ~3(CONS T (CDR X)).~1
.SKIP 3;
{INDENT 0,0;⎇A ~3CREATE~1 expression translates into an appropriate
INTERLISP form using ↓_cons_↓, ↓_list_↓, ↓_put_↓, ↓_putl_↓,
↓_puthash_↓, ↓_seta_↓, etc., that creates the new datum with the
various fields initialized to the appropriate values.
If values are neither explicitly specified, nor implicitly specified
via ~3USING~1 or ~3COPYING~1, the DEFAULT value in the
declaration is used, if any,{XNOTE; SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \For ~3RECORD~1 and ~3TYPERECORD~1 declarations
with non-~3NIL~1 defaults, all elements and named tails will be
initialized; unnamed tails will ~2not~1 be initialized. For example,
~3(RECORDα FOOα (Aα NILα B)α DEFAULT←T) will
cause ~3(CREATEα FOO)~1 to construct
~3(Tα Tα T)~1 not ~3(Tα Tα Tα .α T)~1. Of course,
~3(RECORDα FOOα (Aα Bα .α C)α DEFAULT←T)~1
will cause ~3(CREATEα FOO)~1 to construct
~3(Tα Tα .α T)~1 as expected.
.SKIP 2;
otherwise ~3NIL~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;⎇{!NOTE!⎇ \For ~3PROPRECORD~1, initialization
is only performed where necessary. For example,
~3(RECORDα FOOα (Aα B)α (PROPRECORDα (Cα Dα E)))~1 would cause
~3(CREATEα FOO)~1 to construct ~3(NILα NIL)~1,
not ~3(NILα (Cα NILα Dα NILα Eα NIL))~1.
however, will construct ~3(NILα (Cα Tα Dα Tα Eα T))~1.
.SKIP 2;
{INDEXX(|END CREATE (record package)|)⎇
.SKIP 4;
.KEEP 15;
.SKIP 1 << for break >>
.SKIP 2;
{INDENT 0,0;⎇Record operations are implemented by replacing
expressions of the form ~3X:FOO~1 by
{INDEXX(|FETCH (use in records
 in clisp)|)⎇
~3(FETCHα FOOα OFα X)~1, and ~3X:FOO←Y~1 by
{INDEXX(|REPLACE (use in records in clisp)|)⎇~3(REPLACEα FOOα OFα Xα
recognizes expressions input in this form.
.SKIP 2;
and then storing the translation elsewhere, usually in a hash array,
as described on {PAGEREF L!5⎇. Translations of ~3CREATE~1 expressions
are also stored elswhere.
.SKIP 3;
The translation of each record operation is computed using
information retrieved from the property list of the field name, under
Thus, (global) field names must be unique, i.e. cannot be the same as
the name of any other field in any other record. {L!42:⎇Records can
also be declared local to a particular function by using a CLISP
declaration, as described on {PAGEREF L!41⎇. Local record
declarations override global ones, and a local record can have a
field name the same as that of a local record of another function, or
the same as a field name of a global record.
.SKIP 1 << for break >>
.KEEP 5; SKIP 2;
{INDENT 0,0;⎇For both global and local records, the translation is
computed using all CLISP declarations in effect as described on
{PAGEREF L!14⎇, e.g. if the declaration ~3UNDOABLE~1 in in effect,
~3/RPLACA, /RPLACD, /PUTHASH~1, etc. will be used.
.SKIP 1 << for break >>
.KEEP 5; SKIP 2;
{INDENT 0,0;⎇When the user redeclares a global record, the
translations of all expressions involving that record are
automatically deleted,{XNOTE; SEND FOOT ⊂ {INDENT 0,4;⎇{!NOTE!⎇ \from
↓_clisparray_↓. If the user is not using this method for storing
translations, i.e. is instead using the CLISP%_ method
those expressions already translated will remain as they are.
(There is no practical way to locate them.)
.SKIP 2;
and thus will be recomputed using the new information. If the user
changes a ~2local~* record declaration, or changes some other CLISP
declaration, e.g. ~3STANDARD~1 to ~3FAST~1, and wishes the new
information to affect record expressions already translated, he must
make sure the corresponding translations are removed, usually either
by CLISPIFYING or changing the expression by editing it.
 record package (in clisp)|)⎇