Chapter 14. Data-type Declarations

In MDL, it is possible to declare the permissible range of "types" and/or structures that an ATOM's values or a function's arguments or value may have. This is done using a special TYPE, the DECL ("declaration"). A DECL is of PRIMTYPE LIST but has a complicated internal structure. DECLs are used by the interpreter to find TYPE errors in function calling and by the compiler to generate more efficient code.

There are two kinds of DECLs. The first kind of DECL is the most common. It is called the ATOM DECL and is used most commonly to specify the type/structure of the LVALs of the ATOMs in the argument LIST of a FUNCTION or aux LIST of a PROG or REPEAT. This DECL has the form:

#DECL (atoms:list Pattern ...)

where the pairing of a LIST of ATOMs and a "Pattern" can be repeated indefinitely. This declares the ATOMs in a list to be of the type/structure specified in the following Pattern. The special ATOM VALUE, if it appears, declares the result of a FUNCTION call or PROG or REPEAT evaluation to satisfy the Pattern specified. An ATOM DECL is useful in only one place: immediately following the argument LIST of a FUNCTION, PROG, or REPEAT. It normally includes ATOMs in the argument LIST and ATOMs whose LVALs are otherwise used in the Function body.

The second kind of DECL is rarely seen by the casual MDL user, except in appendix 2. It is called the RSUBR DECL. It is used to specify the type/structure of the arguments and result of an RSUBR or RSUBR-ENTRY (chapter 19). It is of the following form:

#DECL ("VALUE" Pattern Pattern ...)

where the STRING "VALUE" precedes the specification of the type/structure of the value of the call to the RSUBR, and the remaining Patterns specify the arguments to the RSUBR in order. The full specification of the RSUBR DECL will be given in section 14.9. The RSUBR DECL is useful in only one place: as an element of an RSUBR or RSUBR-ENTRY.

14.1. Patterns

The simplest possible Pattern is to say that a value is exactly some other object, by giving that object, QUOTEd. For example, to declare that a variable is a particular ATOM:

#DECL ((X) 'T)

declares that .X is always the ATOM T. When variables are DECLed as "being" some other object in this way, the test used is =?, not ==?. The distinction is usually not important, since ATOMs, which are most commonly used in this construction, are ==? to each other is =? anyway.

It is more common to want to specify that a value must be of a given TYPE. This is done with the simplest non-specific Pattern, a TYPE name. For example,


declares .X to be of TYPE FIX, and .Y of TYPE FLOAT. In addition to the names of all of the built-in and created TYPEs, such as FIX, FLOAT and LIST, a few "compound" type names are allowed:

  • ANY allows any TYPE.
  • STRUCTURED allows any structured TYPE, such as LIST, VECTOR, FALSE, CHANNEL, etc. (appendix 3).
  • LOCATIVE allows any locative TYPE, such as are returned by LLOC, GLOC, AT, and so on (chapter 12).
  • APPLICABLE allows any applicable TYPE, such as FUNCTION, SUBR, FIX (!), etc. (appendix 3).
  • Any other ATOM can be used to stand for a more complex construct, if an association is established on that ATOM and the ATOM DECL. A common example is to <PUT NUMBER DECL '<OR FIX FLOAT>> (see below), so that NUMBER can be used as a "compound type name".

The single TYPE name can be generalized slightly, allowing anything of a given PRIMTYPE, using the following construction:


This construction consists of a two-element FORM, where the first element is the ATOM PRIMTYPE, and the second the name of a primitive type.

The next step is to specify the elements of a structure. This is done in the simplest way as follows:

< structured:type Pattern Pattern ...>

where there is a one-to-one correspondence between the Pattern and the elements of the structure. For example:


declares .X to be a VECTOR having at least two elements, the first of which is a FIX and the second a FLOAT. It is often convenient to allow additional elements, so that only the elements being used in the local neighborhood of the DECL need to be declared. To disallow additional elements, a SEGMENT is used instead of a FORM (the "excl-ed" brackets make it look more emphatic). For example:


declares .X to be a VECTOR having exactly two elements, the first of which is a FIX and the second a FLOAT. Note that the Patterns given for elements can be any legal Pattern:


declares .X to be a VECTOR containing another VECTOR of at least two elements, and .Y to be of PRIMTYPE LIST, containing a LIST. In the case of a BYTES, the individual elements cannot be declared (they must be FIXes anyway), only the size and number of the bytes:

#DECL ((B) <BYTES 7 3>)

declares .B to be a BYTES with BYTE-SIZE 7 and at least three elements.

It is possible to say that some number of elements of a structure satisfy a given Pattern (or sequence of Patterns). This is called an "NTH construction".

[ number:fix Pattern Pattern ... ]

states that the sequence of Patterns which is REST of the VECTOR is repeated the number of times given. For example:


.X is declared to contain three FIXes and a FLOAT, perhaps followed by other elements. .Y is declared to repeat the sequence FIX-FLOAT three times. Note that there may be more repetitions of the sequence in .Y (but not in .X): the DECL specifies only the first six elements.

For indefinite repetition, the same construction is used, but, instead of the number of repetitions of the sequence of Patterns, the ATOM REST is given. This allows any number of repetitions, from zero on up. For example:


A "REST construction" can contain any number of Patterns, just like an NTH construction:


declares that .X is a VECTOR wherein the sequence FIX-FLOAT-LIST repeats indefinitely. It does not declare that <LENGTH .X> is an even multiple of three: the VECTOR can end at any point.

A variation on REST is OPT (or OPTIONAL), which is similar to REST except that the construction is scanned once at most instead of indefinitely, and further undeclared elements can follow. For example:


declares that .X is a VECTOR which is empty or whose first element is a FIX. Only a REST construction can follow an "OPT construction".

Note that the REST construction must always be the last element of the structure declaration, since it gives a Pattern for the rest of the structure. Thus, the REST construction is different from all others in that it has an unlimited range. No matter how many times the Pattern it gives is RESTed off of the structure, the remainder of the structure still has that Pattern.

This exhausts the possible single Patterns that can be given in a declaration. However, there is also a compound Pattern defined. It allows specification of several possible Patterns for one value:

<OR Pattern Pattern ... >

Any non-compound Pattern can be included as one of the elements of the compound Pattern. Finally, compound Patterns can be used as Patterns for elements of structures, and so on.


The OR construction can be extended to any level of ridiculousness, but the higher the level of complexity and compoundedness the less likely the compiler will find the DECL useful.

At the highest level, any Pattern at top level in an ATOM DECL can be enclosed in the construction

< specialty:atom Pattern >

which explicitly declares the specialty of the ATOM(s) in the preceding LIST. specialty can be either SPECIAL or UNSPECIAL. Specialty is important only when the program is to be compiled. The word comes from the control stack, which is called "special" in Lisp (Moon, 1974) because the garbage collector finds objects on it and modifies their internal pointers when storage is compacted. (An internal stack is used within the interpreter and is not accessible to programs -- section 22.1) In an interpreted program all local values are inherently SPECIAL, because all bindings are put on the control stack (but see SPECIAL-MODE below). When the program is compiled, only values declared SPECIAL (which may or may not be the declaration used by default) remain in bindings on the control stack. All others are taken care of simply by storing objects on the control stack: the ATOMs involved are not needed and are not created on loading. So, a program that SETs an ATOM's local value for another program to pick up must declare that ATOM to be SPECIAL. If it doesn't, the ATOM's binding will go away during compiling, and the program that needs to refer to the ATOM will either get a no-value error or refer to an erroneous binding. Usually only ATOMs which have the opposite specialty from that of the current SPECIAL-MODE are explicitly declared. The usual SPECIAL-MODE is UNSPECIAL, so typically only SPECIAL declarations use this construction:


explicitly declares ACT to be SPECIAL.

Most well-written, modular programs get all their information from their arguments and from GVALs, and thus they rarely use SPECIAL ATOMs, except perhaps for ACTIVATIONs and the ATOMs whose LVALs MDL uses by default: INCHAN, OUTCHAN, OBLIST, DEV, SNM, NM1, NM2. OUTCHAN is a special case: the compiler thinks that all conversion-output SUBRs are called with an explicit CHANNEL argument, whether or not the program being compiled thinks so. For example, <CRLF> is compiled as though it were <CRLF .OUTCHAN>. So you may use (or see) the binding (OUTCHAN .OUTCHAN) in an argument LIST, however odd that may appear, because that -- coupled with the usual UNSPECIAL declaration by default -- makes only one reference to the current binding of OUTCHAN and stuffs the result in a slot on the stack for use within the Function.

14.2. Examples


declares .Q to be either a VECTOR or a CHANNEL.


declares .P, .Q, .R, and .S all to be of PRIMTYPE LIST.

#DECL ((F) <FORM [3 ANY]>)

declares .F to be a FORM whose length is at least three, containing objects of any old TYPE.


declares .LL to be of PRIMTYPE LIST, and to have at least four elements, each of which are LISTs of unspecified length (possibly empty) containing FIXes.


declares .VV to be a VECTOR with at least three elements. Those elements are, in order, of TYPE FIX, ATOM, and CHARACTER.


declares .EH to be a LIST whose first element is an ATOM and the rest of whose elements are FLOATs. It also says that .EH is at least one element long.


declares .FOO to be a LIST whose odd-positioned elements are the ATOM T and whose even-positioned elements are FIXes.

<MAPR <>
      <FUNCTION (X)
        #DECL ((X) <VECTOR [1 FIX]>)
        <PUT .X 1 0>>

declares .X to be a VECTOR containing at least one FIX. The more restrictive [REST FIX] would take excessive checking time by the interpreter, because the REST of the VECTOR would be checked on each iteration of the MAPR. In this case both DECLs are equally powerful, because checking the first element of all the RESTs of a structure eventually checks all the elements. Also, since the FUNCTION refers only to the first element of X, this is as much declaration as the compiler can effectively use. (If this VECTOR always contains only FIXes, it should be a UVECTOR instead, for space efficiency. Then a [REST FIX] DECL would make the interpreter check only the UTYPE. If the FIXes cover a small non-negative range, then a BYTES might be even better, with a DECL of <BYTES n 0>.)

        #DECL ((N) <UNSPECIAL FIX>)
        <COND (<0? .N> 1) (ELSE <* .N <FACT <- .N 1>>>)>>

declares .N to be of TYPE FIX and UNSPECIAL. This specialty declaration ensures that, independent of SPECIAL-MODE during compiling, .N gets compiled into a fast control-stack reference.

<PROG ((L (0))
               (N <UNSPECIAL FIX>))
        <COND (<0? .N> <RETURN .L>)>
        <SET L (<+ .N <1 .L>> !.L)>
        <SET N <- .N 1>>>

The above declares L and N to be UNSPECIAL, says that .N is a FIX, and says that .L, along with the value returned, is a LIST of any length composed entirely of FIXes.

14.3. The DECL Syntax

This section gives quasi-BNF productions for the MDL DECL syntax. In the following table MDL type-specifiers are distinguished in this way.

decl    ::=     #DECL (declprs)

declprs ::=     (atlist) pattern | declprs declprs

atlist  ::=     atom | atom atlist

pattern ::=     pat | <UNSPECIAL pat> | <SPECIAL pat>

pat     ::=     unit | <OR unit ... unit>

unit    ::=     type | <PRIMTYPE type> | atom | 'any
                | <struc elts> | <<OR struc ... struc> elts>
                | !<struc elts> | !<<OR struc ... struc> elts>
                | <bstruc fix> | <bstruc fix fix>
                | !<bstruc fix fix>

struc   ::=     structured-type | <PRIMTYPE structured-type>

bstruc  ::=     BYTES | <PRIMTYPE BYTES>

elts    ::=     pat | pat elts
                | [fix pat ... pat]
                | [fix pat ... pat] elts
                | [opt pat ... pat] | [REST pat ... pat]
                | [opt pat ... pat] [REST pat ... pat]

opt     ::=     OPT | OPTIONAL

14.4. Good DECLs

There are some rules of thumb concerning "good" DECLs. A "good" DECL is one that is minimally offensive to the DECL-checking mechanism as the compiler, but that gives the maximum amount of information. It is simple to state what gives offense to the compiler and DECL-checking mechanism: complexity. For example, a large compound DECL like:


is a DECL that the compiler will find totally useless. It might as well be ANY. The more involved the OR, the less information the compiler will find useful in it. For example, if the function takes <OR LIST VECTOR UVECTOR>, maybe you should really say STRUCTURED. Also, a very general DECL indicates a very general program, which is not likely to be efficient when compiled (of course there is a trade-off here). Narrowing the DECL to one PRIMTYPE gives a great gain in compiled efficiency, to one TYPE still more.

Another situation to be avoided is the ordinary large DECL, even if it is perfectly straightforward. If you have created a structure which has a very specific DECL and is used all over your code, it might be better as a NEWTYPE (see below). The advantage of a NEWTYPE over a large explicit DECL is twofold. First, the entire structure must be checked only when it is created, that is, CHTYPEd from its PRIMTYPE. As a full DECL, it is checked completely on entering each function and on each reassignment of ATOMs DECLed to be it. Second, the amount of storage saved in the DECLs of FUNCTIONs and so on is large, not to mention the effort of typing in and keeping up to date several instances of the full DECL.

14.5. Global DECLs

14.5.1. GDECL and MANIFEST

There are two ways to declare GVALs for the DECL-checking mechanism. These are through the FSUBR GDECL ("global declaration") and the SUBR MANIFEST.

<GDECL atoms:list Pattern ...>

GDECL allows the type/structure of global values to be declared in much the same way as local values. Example:


declares ,X to be a FIX, and ,Y to be a LIST containing at least one FIX.

<MANIFEST atom atom ...>

MANIFEST takes as arguments ATOMs whose GVALs are declared to be constants. It is used most commonly to indicate that certain ATOMs are the names of offsets in structures. For example:

<SETG X 1>

allows the compiler to confidently open-compile applications of X (getting the first element of a structure), knowing that ,X will not change. Any sort of object can be a MANIFEST value: if it does not get embedded in the compiled code, it is included in the RSUBR's "reference vector", for fast access. However, as a general rule, structured objects should not be made MANIFEST: the SETG will instead refer to a distinct copy of the object in each RSUBR that does a GVAL. A structured object should instead be GDECLed.

An attempt to SETG a MANIFEST atom will cause an error, unless either:

  1. the ATOM was previously globally unassigned;
  2. the old value is ==? to the new value; or
  3. .REDEFINE is not FALSE.


<MANIFEST? atom>

returns T if atom is MANIFEST, #FALSE () otherwise.

<UNMANIFEST atom atom ...>

removes the MANIFEST of the global value of each of its arguments so that the value can be changed.

14.5.3. GBOUND?

<GBOUND? atom>

("globally bound") returns T if atom has a global value slot (that is, if it has ever been SETGed, MANIFEST, GDECLed, or GLOCed (chapter 12) with a true second argument), #FALSE () otherwise.

14.6. NEWTYPE (again)

NEWTYPE gives the programmer another way to DECL objects. The third (and optional) argument of NEWTYPE is a QUOTEd Pattern. If given, it will be saved as the value of an association (chapter 13) using the name of the NEWTYPE as the item and the ATOM DECL as the indicator, and it will be used to check any object that is about to be CHTYPEd to the NEWTYPE. For example:


creates a new TYPE, with its first two elements declared to be FLOATs. If later someone types:


an error will result (the second element is not a FLOAT). The Pattern can be replaced by doing another NEWTYPE for the same TYPE, or by putting a new value in the association. Further examples:


causes FOOs to contain a FIX and a FLOAT and any number of ATOMs.


<SET A #BAR (#BAR () 1 1.2 GRITCH)>


This is an example of a recursively DECLed TYPE. Note that <1 .A> does not satisfy the DECL, because it is empty, but it was CHTYPEd before the DECL was associated with BAR. Now, even <CHTYPE <1 .A> <TYPE <1 .A>>> will cause an error.

In each of these examples, the <<PRIMTYPE ...> ...> construction was used, in order to permit CHTYPEing an object into itself. See what happens otherwise:

<SET A <CHTYPE (E 2.71828) OOPS>>$
#OOPS (E 2.71828)

Now <CHTYPE .A OOPS> will cause an error. Unfortunately, you must

#OOPS (E 2.71828)

14.7. Controlling DECL Checking

There are several SUBRs and FSUBRs in MDL that are used to control and interact with the DECL-checking mechanism.

14.7.1. DECL-CHECK

This entire complex checking mechanism can get in the way during debugging. As a result, the most commonly used DECL-oriented SUBR is DECL-CHECK. It is used to enable and disable the entire DECL-checking mechanism.

<DECL-CHECK false-or-any>

If its single argument is non-FALSE, DECL checking is turned on; if it is FALSE, DECL checking is turned off. The previous state is returned as a value. If no argument is given, DECL-CHECK returns the current state. In an initial MDL DECL checking is on.

When DECL checking is on, the DECL of an ATOM is checked each time it is SET, the arguments and results of calls to FUNCTIONs, RSUBRs, and RSUBR-ENTRYs are checked, and the values returned by PROG and REPEAT are checked. The same is done for SETGs and, in particular, attempts to change MANIFEST global values. Attempts to CHTYPE an object to a NEWTYPE (if the NEWTYPE has the optional DECL) are also checked. When DECL checking is off, none of these checks is performed.


<SPECIAL-CHECK false-or-any>

controls whether or not SPECIAL checking is performed at run time by the interpreter. It is initially off. Failure to declare an ATOM to be SPECIAL when it should be will produce buggy compiled code.

<SPECIAL-MODE specialty:atom>

sets the declaration used by default (for ATOMs not declared either way) and returns the previous such declaration, or the current such declaration if no argument is given. The initial declaration used by default is UNSPECIAL.

14.7.3. GET-DECL and PUT-DECL

GET-DECL and PUT-DECL are used to examine and change the current DECL (of either the global or the local value) of an ATOM.

<GET-DECL locd>

returns the DECL Pattern (if any, otherwise #FALSE ()) associated with the global or local value slot of an ATOM. For example:

      #DECL ((X) <OR FIX FLOAT>)
      <GET-DECL <LLOC X>>

would return <OR FIX FLOAT> as the result of the application of GET-DECL. Note that because of the use of LLOC (or GLOC, for global values) the ATOM being examined must be bound; otherwise you will get an error! This can be gotten around by testing first with BOUND? (or GBOUND?, or by giving GLOC a second argument which is not FALSE).

If the slot being examined is the global slot and the value is MANIFEST, then the ATOM MANIFEST is returned. If the value being examined is not DECLed, #FALSE () is returned.

<PUT-DECL locd Pattern>

makes Pattern be the DECL for the value and returns locd. If <DECL-CHECK> is true, the current value must satisfy the new Pattern. PUT-DECL is normally used in debugging, to change the DECL of an object to correspond to changes in the program. Note that it is not legal to PUT-DECL a "Pattern" of MANIFEST or #FALSE ().

14.7.4. DECL?

<DECL? any Pattern>

specifically checks any against Pattern. For example:

<DECL? '[1 2 3] '<VECTOR [REST FIX]>>$
<DECL? '[1 2.0 3.0] '<VECTOR [REST FIX]>>$

14.8. OFFSET

An OFFSET is essentially a FIX with a Pattern attached, considered as an APPLICABLE rather than a number. An OFFSET allows a program to specify the type of structure that its FIX applies to. OFFSETs, like DECLs -- if used properly -- can make debugging considerably easier; they will eventually also help the compiler generate more efficient code.

The SUBR OFFSET takes two arguments, a FIX and a Pattern, and returns an object of TYPE and PRIMTYPE OFFSET. An OFFSET, like a FIX, may be given as an argument to NTH or PUT and may be applied to arguments. The only difference is that the STRUCTURED argument must match the Pattern contained in the OFFSET, or an error will result. Thus:


Note: when the compiler gets around to understanding OFFSETs, it will not do the right thing with them unless they are MANIFEST. Since there's no good reason not to MANIFEST them, this isn't a problem.

The SUBR INDEX, given an OFFSET, returns its FIX:


GET-DECL of an OFFSET returns the associated Pattern; PUT-DECL of an OFFSET and a Pattern returns a new OFFSET with the same INDEX as the argument, but with a new Pattern:


An OFFSET is not a structured object, as this example should make clear.

14.9. The RSUBR DECL

The RSUBR DECL is similar to the ATOM DECL, except that the declarations are of argument positions and value rather than of specific ATOMs. Patterns can be preceded by STRINGs which further describe the argument (or value).

The simplest RSUBR DECL is for an RSUBR or RSUBR-ENTRY (chapter 19) which has all of its arguments evaluated and returns a DECLed value. For example:


declares that there are two arguments, a FIX and a FLOAT, and a result which is a FIX. While the STRING "VALUE" is not constrained to appear at the front of the DECL, it does appear there by custom. It need not appear at all, if the result is not to be declared, but (again by custom) in this case it is usually declared ANY.

If any arguments are optional, the STRING "OPTIONAL" (or "OPT") is placed before the Pattern for the first optional argument:


If any of the arguments is not to be evaluated, it is preceded by the STRING "QUOTE":


declares one argument, which is not EVALed.

If the arguments are to be evaluated and gathered into a TUPLE, the Pattern for it is preceded by the STRING "TUPLE":


If the arguments are to be unevaluated and gathered into a LIST, or if the calling FORM is the only "argument", the Pattern is preceded by the appropriate STRING:



In every case the special indicator STRING is followed by a Pattern which describes the argument, even though it may sometimes produce fairly ludicrous results, since the pattern for "TUPLE" always must be a TUPLE; for "ARGS", a LIST; and for "CALL", a FORM or SEGMENT.