The pymdl book
17. 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 A. 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 33). 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.
pymdl's own. This chapter is the one the rest of the book leans on hardest, and it is worth saying why up front.
DECLs are the only place MDL records a program's intentions about types, so they are what chapter 34's compiler reads to decide what it can compile and how; they are what chapter 30's packages use to describe an entry point; and appendix A's table of built-ins isRSUBRDECLs in prose. ADECLis checked here by an interpreter that walks the Pattern, which means a Pattern this chapter describes and pymdl gets wrong would be a wrong answer, not a missing optimization. Every construction below is tested.
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 if =? 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,
#DECL ((X) FIX (Y) FLOAT)
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:
ANYallows anyTYPE.STRUCTUREDallows any structuredTYPE, such asLIST,VECTOR,FALSE,CHANNEL, etc. (appendix B).LOCATIVEallows any locativeTYPE, such as are returned byLLOC,GLOC,AT, and so on (chapter 15).APPLICABLEallows any applicableTYPE, such asFUNCTION,SUBR,FIX(!), etc. (appendix B).- Any other
ATOMcan be used to stand for a more complex construct, if an association is established on thatATOMand theATOMDECL. A common example is to<PUT NUMBER DECL '<OR FIX FLOAT>>(see below), so thatNUMBERcan be used as a "compound type name".
The whole of this section, tested with DECL? (section 14.7.4) so each Pattern
can be seen answering:
<DECL? T ''T> ⇒ T
<DECL? 1 'FIX> ⇒ T
<DECL? 1 'ANY> ⇒ T
<DECL? '(1) 'STRUCTURED> ⇒ T
<DECL? ,+ 'APPLICABLE> ⇒ T
<DECL? 1 'APPLICABLE> ⇒ T
The exclamation point after FIX in the APPLICABLE list is the manual's own,
and the last line earns it: a FIX really is applicable, by section 7.1.6.
And the last bullet, which is how a program names its own types:
<PUT NUMBER DECL '<OR FIX FLOAT>> ⇒ NUMBER
<DECL? 1 'NUMBER> ⇒ T
<DECL? 2.0 'NUMBER> ⇒ T
<DECL? "s" 'NUMBER> ⇒ #FALSE ()
That is chapter 16's association machinery, used by the type system itself --
the same pattern as the COMMENT indicator there.
The single TYPE name can be generalized slightly, allowing anything of a given
PRIMTYPE, using the following construction:
#DECL ((X) <PRIMTYPE WORD> (Y) <PRIMTYPE LIST>)
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.
<DECL? 1 '<PRIMTYPE WORD>> ⇒ T
<DECL? '<+ 1 2> '<PRIMTYPE LIST>> ⇒ T
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 Patterns and the elements of the structure. For example:
#DECL ((X) <VECTOR FIX FLOAT>)
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:
#DECL ((X) !<VECTOR FIX FLOAT>)
declares .X to be a VECTOR having exactly two elements, the first of
which is a FIX and the second a FLOAT.
"At least" versus "exactly" is one character of difference and it is visible:
<DECL? '[1 2 3] '<VECTOR FIX FIX>> ⇒ T
<DECL? '[1 2 3] '!<VECTOR FIX FIX>> ⇒ #FALSE ()
<DECL? '[1 2] '!<VECTOR FIX FIX>> ⇒ T
Note that the Patterns given for elements can be any legal Pattern:
#DECL ((X) <VECTOR <VECTOR FIX FLOAT>> (Y) <<PRIMTYPE LIST> LIST>)
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:
#DECL ((X) <VECTOR [3 FIX] FLOAT> (Y) <LIST [3 FIX FLOAT]>)
.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:
#DECL ((X) <VECTOR [REST FIX]> (Y) <LIST [3 FIX] [REST FIX]>)
A "REST construction" can contain any number of Patterns, just like an NTH
construction:
#DECL ((X) <VECTOR [REST FIX FLOAT LIST]>)
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:
#DECL ((X) <VECTOR [OPT FIX]>)
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".
The three bracket constructions, one line each:
<DECL? '[1 2 3] '<VECTOR [3 FIX]>> ⇒ T
<DECL? '[1 2 3] '<VECTOR [REST FIX]>> ⇒ T
<DECL? '[1 2.0 3.0] '<VECTOR [REST FIX]>> ⇒ #FALSE ()
<DECL? '[1] '<VECTOR [OPT FIX]>> ⇒ T
<DECL? '[] '<VECTOR [OPT FIX]>> ⇒ T
<DECL? '(T 1 T 2) '<LIST [REST 'T FIX]>> ⇒ T
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.
<DECL? 1 '<OR FIX FLOAT>> ⇒ T
<DECL? '(1 2) '<OR FIX <LIST [REST FIX]>>> ⇒ T
<DECL? '![1 2] '<OR FIX <UVECTOR [REST <OR FIX FLOAT>]>>> ⇒ T
<DECL? '![1.0 2.0] '<OR FIX <UVECTOR [REST <OR FIX FLOAT>]>>> ⇒ T
'![1 2.0] ⇒ *ERROR* TYPES-DIFFER-IN-UNIFORM-VECTOR
The last line is the point of the nested OR, and it is a UVECTOR rule rather
than a DECL rule: a UVECTOR cannot hold a FIX and a FLOAT at once
(section 7.6.5), so READ rejects the mixture before any Pattern is consulted.
The inner <OR FIX FLOAT> therefore permits a UVECTOR of FIXes or a
UVECTOR of FLOATs, never one containing both.
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.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:
#DECL ((ACT) <SPECIAL ACTIVATION>)
explicitly declares ACT to be SPECIAL. (The manual prints an extra ) after
ACT.)
pymdl's own. This paragraph is the single most load-bearing explanation in the manual for anyone reading chapter 34, and it survives the change of machine completely. Substitute "a Python local" for "a slot on the control stack" and every consequence is the same: an
UNSPECIALlocal has noATOMat run time, so nothing outside the compiled function can see it,LLOCcannot take its address, and aFUNCTIONliteral written inside it cannot close over it. pymdl's compiler therefore refuses those shapes rather than compiling them wrong, and adding<SPECIAL ...>is exactly the fix the manual prescribes. The 1979 advice is not merely still correct; it is still actionable, against a compiler that did not exist when it was written.
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.
That list of seven ATOMs is worth remembering; chapter 14 named the same ones
as the SUBRs' defaults, and (OUTCHAN .OUTCHAN) is the idiom its section
11.2.7 recommends.
14.2. Examples
#DECL ((Q) <OR VECTOR CHANNEL>)
declares .Q to be either a VECTOR or a CHANNEL.
#DECL ((P Q R S) <PRIMTYPE LIST>)
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.
#DECL ((LL) <<PRIMTYPE LIST> [4 <LIST [REST FIX]>]>)
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.
#DECL ((VV) <VECTOR FIX ATOM CHARACTER>)
declares .VV to be a VECTOR with at least three elements. Those elements
are, in order, of TYPE FIX, ATOM, and CHARACTER.
#DECL ((EH) <LIST ATOM [REST FLOAT]>)
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.
#DECL ((FOO) <LIST [REST 'T FIX]>)
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>>
.FOO>
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>.)
The FACT of chapter 11, declared:
<DEFINE FACT (N)
#DECL ((N) <UNSPECIAL FIX>)
<COND (<0? .N> 1) (ELSE <* .N <FACT <- .N 1>>>)>> ⇒ FACT
<FACT 5> ⇒ 120
<FACT 1.0> ⇒ *ERROR* TYPE-MISMATCH
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. Note that the DECL also caught 1.0,
which the undeclared FACT of chapter 11 would have looped on forever, since
<0? 1.0> is never true after repeated subtraction of 1 from 0.5.
<PROG ((L (0)) (N 5))
#DECL ((L VALUE) <UNSPECIAL <LIST [REST FIX]>>
(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. (The manual writes this as a PROG that never repeats and
never binds N, with (N <UNSPECIAL FIX>) inside the DECL where the syntax
of 14.3 wants (N) <UNSPECIAL FIX>; it is shown rather than run.)
14.3. The DECL Syntax
This section gives quasi-BNF productions for the MDL DECL syntax.
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
| ANY | STRUCTURED | LOCATIVE | APPLICABLE
| <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 and 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:
#DECL ((X) <OR FIX LIST UVECTOR FALSE>)
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:
<GDECL (X) FIX (Y) <LIST FIX>> ⇒ T
<SETG X 1> ⇒ 1
<SETG X "s"> ⇒ *ERROR* TYPE-MISMATCH
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> ⇒ 1
<MANIFEST X> ⇒ T
<MANIFEST? X> ⇒ T
<SETG X 2> ⇒ *ERROR* ALREADY-MANIFEST
<SETG X 1> ⇒ 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:
- the
ATOMwas previously globally unassigned; - the old value is
==?to the new value; or .REDEFINEis notFALSE.
Clause 2 is the <SETG X 1> that succeeded above, immediately after the
<SETG X 2> that did not.
14.5.2. MANIFEST? and UNMANIFEST
<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.
<SETG X 1> ⇒ 1
<MANIFEST X> ⇒ T
<UNMANIFEST X> ⇒ T
<MANIFEST? X> ⇒ #FALSE ()
<SETG X 2> ⇒ 2
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 15) with a
true second argument), #FALSE () otherwise.
<SETG X 1> ⇒ 1
<GBOUND? X> ⇒ T
<GBOUND? NEVER-MENTIONED> ⇒ #FALSE ()
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 16) 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:
<NEWTYPE COMPLEX-NUMBER VECTOR
'<<PRIMTYPE VECTOR> FLOAT FLOAT>> ⇒ COMPLEX-NUMBER
<CHTYPE [1.0 2.0] COMPLEX-NUMBER> ⇒ #COMPLEX-NUMBER [1.0 2.0]
creates a new TYPE, with its first two elements declared to be FLOATs. If
later someone types #COMPLEX-NUMBER [1.0 2] 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:
<NEWTYPE FOO LIST '<<PRIMTYPE LIST> FIX FLOAT [REST ATOM]>> ⇒ FOO
<CHTYPE (1 2.0 A B) FOO> ⇒ #FOO (1 2.0 A B)
causes FOOs to contain a FIX and a FLOAT and any number of ATOMs.
<NEWTYPE BAR LIST>
<SET A #BAR (#BAR () 1 1.2 GRITCH)>
<NEWTYPE BAR LIST '<<PRIMTYPE LIST> BAR [REST FIX FLOAT ATOM]>>
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:
<NEWTYPE OOPS LIST '<LIST ATOM FLOAT>> ⇒ OOPS
<SET A <CHTYPE (E 2.71828) OOPS>> ⇒ #OOPS (E 2.7182799)
Now <CHTYPE .A OOPS> will cause an error. Unfortunately, you must
<NEWTYPE OOPS LIST '<LIST ATOM FLOAT>>
<SET A <CHTYPE (E 2.71828) OOPS>>
<CHTYPE <CHTYPE .A LIST> OOPS> ⇒ #OOPS (E 2.7182799)
The reason is worth stating plainly, because it catches everyone: the Pattern
<LIST ATOM FLOAT> requires the object's TYPE to be LIST, and an OOPS is
not a LIST even though its PRIMTYPE is. Writing <<PRIMTYPE LIST> ...>
asks about the PRIMTYPE instead, which an OOPS does satisfy.
(2.71828 prints as 2.7182799, which is chapter 9's single-precision FLOAT,
not a DECL matter.)
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.
<DECL-CHECK> ⇒ T
<DECL-CHECK <>> ⇒ T
<DECL-CHECK> ⇒ #FALSE ()
<DECL-CHECK T> ⇒ #FALSE ()
<DECL-CHECK> ⇒ T
Every answer is the previous state, which is what makes it possible to turn checking off and put it back the way it was.
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.
14.7.2. SPECIAL-CHECK and SPECIAL-MODE
<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.
<SPECIAL-CHECK> ⇒ #FALSE ()
<SPECIAL-MODE> ⇒ UNSPECIAL
<SPECIAL-MODE SPECIAL> ⇒ UNSPECIAL
<SPECIAL-MODE> ⇒ SPECIAL
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:
<PROG (X)
#DECL ((X) <OR FIX FLOAT>)
<GET-DECL <LLOC X>>> ⇒ <OR FIX FLOAT>
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.
<SETG G 1> ⇒ 1
<GET-DECL <GLOC G>> ⇒ #FALSE ()
<MANIFEST G> ⇒ T
<GET-DECL <GLOC G>> ⇒ MANIFEST
<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 ().
<SET Y 1> ⇒ 1
<PUT-DECL <LLOC Y> 'FIX>
<GET-DECL <LLOC Y>> ⇒ FIX
<SET Y 2> ⇒ 2
<SET Y "s"> ⇒ *ERROR* TYPE-MISMATCH
Found along the way. That last line succeeded here until 2026-09-10, and section 14.7.1 is what condemns it: "the
DECLof anATOMis checked each time it isSET." pymdl checked theDECLan enclosingFUNCTIONorPROGhad declared, andSETGchecked aPUT-DECL'd global -- but aDECLplaced on a top-level binding byPUT-DECLwas recorded, answered correctly byGET-DECL, and then never consulted again, because at top level there is no#DECLframe forSETto look in. Which is precisely the situation the section saysPUT-DECLis for: "normally used in debugging, to change theDECLof an object to correspond to changes in the program."SETnow also looks up the DECL recorded against the binding itself, which is the same identityGET-DECLwas already using.
14.7.4. DECL?
<DECL? any Pattern>
specifically checks any against Pattern. For example:
<DECL? '[1 2 3] '<VECTOR [REST FIX]>> ⇒ T
<DECL? '[1 2.0 3.0] '<VECTOR [REST FIX]>> ⇒ #FALSE ()
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:
<SETG FOO <OFFSET 1 '<CHANNEL FIX>>> ⇒ %<OFFSET 1 '<CHANNEL FIX>>
<TYPE ,FOO> ⇒ OFFSET
<FOO <ROOT>> ⇒ *ERROR* ARG-WRONG-TYPE
<FOO ,INCHAN> works, because ,INCHAN is a CHANNEL whose first element is a
FIX; <FOO <ROOT>> fails, because an OBLIST is not a CHANNEL. (The manual
prints 1 for <FOO ,INCHAN>, which is the era terminal channel's number; the
number a channel is given is an operating-system matter and is not the same
here.)
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:
<SETG FOO <OFFSET 1 '<CHANNEL FIX>>>
<INDEX ,FOO> ⇒ 1
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:
<SETG FOO <OFFSET 1 '<CHANNEL FIX>>>
<GET-DECL ,FOO> ⇒ <CHANNEL FIX>
<PUT-DECL ,FOO OBLIST> ⇒ %<OFFSET 1 OBLIST>
,FOO ⇒ %<OFFSET 1 '<CHANNEL FIX>>
An OFFSET is not a structured object, as this example should make clear: the
PUT-DECL returned a new OFFSET and left ,FOO alone, which is the
opposite of how PUT-DECL behaves on a locative.
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 33) which
has all of its arguments evaluated and returns a DECLed value. For example:
#DECL ("VALUE" FIX FIX FLOAT)
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:
#DECL ("VALUE" FIX FIX "OPTIONAL" FLOAT)
If any of the arguments is not to be evaluated, it is preceded by the STRING
"QUOTE":
#DECL ("VALUE" FIX "QUOTE" FORM)
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":
#DECL ("VALUE" FIX "TUPLE" <TUPLE [REST FIX]>)
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:
#DECL ("VALUE" FIX "ARGS" LIST)
#DECL ("VALUE" FIX "CALL" <PRIMTYPE LIST>)
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.
pymdl's own. The
RSUBRDECLis the form appendix A of this book is written in, because it is the only notation MDL has for "what this Subroutine takes and returns". Every indicatorSTRINGabove maps one to one onto an argument-LISTtoken of chapter 12 --"OPTIONAL","QUOTE"(for aQUOTEdATOM),"TUPLE","ARGS","CALL"-- which is not a coincidence: anRSUBRDECLis what aFUNCTION's argumentLISTbecomes once theATOMs have been compiled away, exactly as section 14.1'sUNSPECIALparagraph describes. Reading the two chapters side by side is the shortest route to understanding what compilation does to aFUNCTION, and chapter 34 takes that route.