110 lines
46 KiB
Plaintext
110 lines
46 KiB
Plaintext
XEROX DECL
|
||
2
|
||
|
||
4
|
||
|
||
1
|
||
|
||
DECL
|
||
1
|
||
|
||
4
|
||
|
||
UNSUPPORTED
|
||
INTERNAL
|
||
Uses: SIMPLIFY, LABEL and LAMBDATRAN
|
||
|
||
NOTE TO LYRIC/MEDLEY USERS
|
||
The DECL module is not supported in Lyric/Medley since it uses the DWIM facilities heavily, and DWIM is not supported in Lyric/Medley. It is being released as a LispUsers module only for backward compatibility. The DECL module only runs under the OLD-INTERLISP-T executive, and all the code that uses it will also have to run under this executive. Therefore, you may wish to convert code that uses DECL to something else.
|
||
INTRODUCTION
|
||
The Decl LispUsers package is contained on the file DECL.LCOM. The Decl package requires the LambdaTran package. LAMBDATRAN.LCOM will automatically be loaded with Decl if it is not already present.
|
||
The Decl package extends Interlisp to allow the user to declare the types of variables and expressions appearing in functions. It provides a convenient way of constraining the behavior of programs when the generality and flexibility of ordinary Interlisp is either unnecessary, confusing, or inefficient.
|
||
Decl provides a simple language for declarations, and augments the interpreter and the compiler to guarantee that these declarations are always satisfied. The declarations make programs more readable by indicating the type, and therefore something about the intended usage, of variables and expressions in the code. They facilitate debugging by localizing errors that manifest themselves as type incompatibilities. Finally, the declaration information is available for other purposes: compiler macros can consult the declarations to produce more efficient code; coercions for arguments at user interfaces can be automatically generated; and the declarations will be noticed by the Masterscope function analyzer.
|
||
The declarations interpreted by the Decl package are in terms of a set of declaration types called decltypes, each of which specifies a set of acceptable values and also (optionally) other type-specific behavior. The Decl package provides a set of facilities for defining decltypes and their relations to each other, including type-valued expressions and a comprehensive treatment of union types.
|
||
The following description of the Decl package is divided into three parts. First, the syntactic extensions that permit the concise attachment of declarations to program elements are discussed. Second, the mechanisms by which new decltypes can be defined and manipulated are covered. Finally, some additional capabilities based on the availability of declarations are outlined.
|
||
USING DECLARATIONS IN PROGRAMS
|
||
Declarations may be attached to the values of arbitrary expressions and to LAMBDA and PROG variables throughout (or for part of) their lexical scope. The declarations are attached using constructs that resemble the ordinary Interlisp LAMBDA, PROG, and PROGN, but which also permit the expression of declarations. The following examples illustrate the use of declarations in programs.
|
||
Consider the following definition for the factorial function (FACT N ):
|
||
[LAMBDA (N)
|
||
(COND
|
||
((EQ N 0) 1)
|
||
(T (ITIMES N (FACT (SUB1 N]
|
||
Obviously, this function presupposes that N is a number, and the run-time checks in ITIMES and SUB1 will cause an error if this is not so. For instance, (FACT T) will cause an error and print the message NON-NUMERIC ARG T. By defining FACT as a DLAMBDA, the Decl package analog of LAMBDA, this presupposition can be stated directly in the code:
|
||
[DLAMBDA ((N NUMBERP))
|
||
(COND
|
||
((EQ N 0) 1)
|
||
(T (ITIMES N (FACT (SUB1 N]
|
||
With this definition, (FACT T) will result in a NON-NUMERIC ARG T error when the body of the code is executed. Instead, the NUMBERP declaration will be checked when the function is first entered, and a declaration fault will occur. Thus, the message that the user will see will not dwell on the offending value T, but instead give a symbolic indication of what variable and declaration were violated, as follows:
|
||
DECLARATION NOT SATISFIED
|
||
((N NUMBERP) BROKEN):
|
||
The user is left in a break from which the values of variables, e.g., N, can be examined to determine what the problem is.
|
||
The function FACT also makes other presuppositions concerning its argument, N. For example, FACT will go into an infinite recursive loop if N is a number less than zero. Although the user could program an explicit check for this unexpected situation, such coding is tedious and tends to obscure the underlying algorithm. Instead, the requirement that N not be negative can be succinctly stated by declaring it to be a subtype of NUMBERP that is restricted to non-negative numbers. This can be done by adding a SATISFIES clause to N's type specification:
|
||
[DLAMBDA ([N NUMBERP (SATISFIES (NOT (MINUSP N])
|
||
(COND
|
||
((EQ N 0) 1)
|
||
(T (ITIMES N (FACT (SUB1 N]
|
||
The predicate in the SATISFIES clause will be evaluated after N is bound and found to satisfy NUMBERP, but before the function body is executed. In the event of a declaration fault, the SATISFIES condition will be included in the error message. For example, (FACT -1) would result in:
|
||
DECLARATION NOT SATISFIED
|
||
((N NUMBERP (SATISFIES (NOT (MINUSP N))) BROKEN):
|
||
The DLAMBDA construct also permits the type of the value that is returned by the function to be declared by means of the pseudo-variable RETURNS. For example, the following definition specifies that FACT is to return a positive integer:
|
||
[DLAMBDA ([N NUMBERP (SATISFIES (NOT (MINUSP N]
|
||
[RETURNS FIXP (SATISFIES (IGREATERP VALUE 0])
|
||
(COND
|
||
((EQ N 0) 1)
|
||
(T (ITIMES N (FACT (SUB1 N]
|
||
After the function body is evaluated, its value is bound to the variable VALUE and the RETURNS declaration is checked. A declaration fault will occur if the value is not satisfactory. This prevents a bad value from propagating to the caller of FACT, perhaps causing an error far away from the source of the difficulty.
|
||
Declaring a variable causes its value to be checked not only when it is first bound, but also whenever that variable is reset by SETQ within the DLAMBDA. In other words, the type-checking machinery will not allow a declared variable to take on an improper value. An iterative version of the factorial function illustrates this feature in the context of a DPROG, the analog of PROG:
|
||
(DLAMBDA ([N NUMBERP (SATISFIES (NOT (MINUSP N]
|
||
[RETURNS FIXP (SATISFIES (IGREATERP VALUE 0])
|
||
[DPROG ([TEMP 1 FIXP (SATISFIES (IGREATERP TEMP 0]
|
||
[RETURNS FIXP (SATISFIES (IGREATERP VALUE 0])
|
||
LP (COND ((EQ N 0) (RETURN TEMP)))
|
||
(SETQ TEMP (ITIMES N TEMP))
|
||
(SETQ N (SUB1 N))
|
||
(GO LP]
|
||
DPROG declarations are much like DLAMBDA declarations, except that they also allow an initial value for the variable to be specified. In the above example, TEMP is declared to be a positive integer throughout the computation and N is declared to be non-negative. Thus, a bug which caused an incorrect value to be assigned by one of the SETQ expressions would cause a declaration failure. Note that the RETURNS declaration for a DPROG is also useful in detecting the common bug of omitting an explicit RETURN.
|
||
DLAMBDAs
|
||
The Decl package version of a LAMBDA expression is an expression beginning with the atom DLAMBDA. Such an expression is a function object that may be used in any context where a LAMBDA expression may be used. It resembles a LAMBDA expression except that it permits declaration expressions in its argument list, as illustrated in the examples given earlier. Each element of the argument list of a DLAMBDA may be a literal atom (as in a conventional LAMBDA) or a list of the form (NAME TYPE .EXTRAS). Strictly, this would require a declaration with a SATISFIES clause to take the form (N (NUMBERP (SATISFIES --)) --). However, due to the frequency with which this construction is used, it may be written without the inner set of parentheses, e.g., (N NUMBERP (SATISFIES --) --).
|
||
NAME fulfills the standard function of a parameter, i.e., providing a name to which the value of the corresponding argument will be bound.
|
||
TYPE is either a Decl package type name or type expression. When the DLAMBDA is entered, its arguments will be evaluated and bound to the corresponding argument names, and then, after all the argument names have been bound, the declarations will be checked. The type checking is delayed so that SATISFIES predicates can include references to other variables bound by the same DLAMBDA. For example, one might wish to define a function whose two arguments are not only both required to be of some given type, but are also required to satisfy some relationship (e.g., that one is less than the other).
|
||
EXTRAS allows some additional properties to be attached to a variable. One such property is the accessibility of NAME outside the current lexical scope. Accessibility specifications include the atoms LOCAL or SPECIAL, which indicate that this variable is to be compiled so that it is either a LOCALVAR or a SPECVAR, respectively. This is illustrated by the following example:
|
||
[DLAMBDA ((A LISTP SPECIAL)
|
||
(B FIXP LOCAL))
|
||
...]
|
||
A more informative equivalent to the SPECIAL key word is the USEDIN form, the tail of which can be a list of the other functions that are expected to have access to the variable.1
|
||
[DLAMBDA ((A LISTP (USEDIN FOO FIE))
|
||
(B FIXP LOCAL))
|
||
...]
|
||
EXTRAS may also include a comment in standard format, so that descriptive information may be given where a variable is bound:
|
||
[DLAMBDA ((A LISTP (USEDIN FOO FIE) (* This is an important variable))
|
||
(B FIXP LOCAL))
|
||
...]
|
||
As mentioned earlier, the value returned by a DLAMBDA can also be declared, by means of the pseudo-variable RETURNS. The RETURNS declaration is just like other DLAMBDA declarations, except (1) in any SATISFIES predicate, the value of the function is referred to by the distinguished name VALUE; and (2) it makes no sense to declare the return value to be LOCAL or SPECIAL.
|
||
DPROG
|
||
Just as DLAMBDA resembles LAMBDA, DPROG is analogous to PROG. As for an ordinary PROG, a variable binding may be specified as an atom or a list including an initial value form. However, a DPROG binding also allows TYPE and EXTRAS information to appear following the initial value form. The format for these augmented variable bindings is (NAME INITIALVALUE TYPE .EXTRAS).
|
||
The only difference between a DPROG binding and a DLAMBDA binding is that the second position is interpreted as the initial value for the variable. Note that if the user wishes to supply a type declaration for a variable, an initial value must be specified. The same rules apply for the interpretation of the type information for DPROGs as for DLAMBDAs, and the same set of optional EXTRAs can be used. DPROGs may also declare the type of the value they return, by specifying the pseudo-variable RETURNS.
|
||
Just as for a DLAMBDA, type tests in a DPROG are not asserted until after all the variables have been bound, thus permitting predicates to refer to other variables being bound by this DPROG. If NIL appears as the initial value for a binding (i.e., the atom NIL actually appears in the code, not simply an expression that evaluates to NIL) the initial type test will be suppressed, but subsequent type tests, e.g., following a SETQ, will still be performed.
|
||
A common construct in Lisp is to bind and initialize a PROG variable to the value of a complicated expression in order to avoid recomputing it, and then to use this value in initializing other PROG variables, e.g.
|
||
[PROG ((A EXPRESSION))
|
||
(RETURN (PROG ((B... ( A...))
|
||
(C... ( A... )))
|
||
...]
|
||
The ugliness of such constructions in conventional Lisp often tempts the programmer to loosen the scoping relationships of the variables by binding them all at a single level and using SETQ's in the body of the PROG to establish the initial values for variables that depend on the initial values of other variables, e.g.,
|
||
[PROG ((A EXPRESSION) B C)
|
||
(SETQ B (...A... ))
|
||
(SETQ C ( ...A... ))
|
||
...]
|
||
In the Decl package environment, this procedure undermines the protection offered by the type mechanism by encouraging the use of uninitialized variables. Therefore, the DPROG offers a syntactic form to encourage more virtuous initialization of its variables. A DPROG variable list may be segmented by occurrences of the special atom THEN, which causes the binding of its variables in stages, so that the bindings made in earlier stages can be used in later ones, e.g.,
|
||
[DPROG ((A (LENGTH FOO) FIXP LOCAL)
|
||
THEN (B (SQRT A) FLOATP)
|
||
THEN (C (CONS A B) LISTP))
|
||
...]
|
||
Each stage is carried out as a conventional set of DPROG bindings (i.e., simultaneously, followed by the appropriate type testing). This layering of the bindings permits one to gradually descend into a inner scope, binding the local names in a very structured and clean fashion, with initial values type-checked as soon as possible.
|
||
DECLARATIONS IN ITERATIVE STATEMENTS
|
||
The CLISP iterative statement provides a very useful facility for specifying a variety of PROGs that follow certain widely used formats. The Decl package allows declarations to be made for the scope of an iterative statement via the DECLARE CLISP (I.S. operator). DECLARE can appear as an operator anywhere in an iterative statement, followed by a list of declarations, for example:
|
||
(for J from 1 to 10 declare (J FIXP) do. . .
|
||
Note that DECLARE declarations do not create bindings, but merely provide declarations for existing bindings. For this reason, an initial value cannot be specified and the form of the declaration is the same as that of DLAMBDAs, namely create (NAME TYPE . EXTRAS).
|
||
Note that variables bound outside of the scope of the iterative statement, i.e., a variable used freely in the I.S., can also be declared using this construction. Such a declaration will only be in effect for the scope of the iterative statement.
|
||
DECLARING A VARIABLE FOR A RESTRICTED LEXICAL SCOPE
|
||
The Decl package also permits declaring the type of a variable over some restricted portion of its existence. For example, suppose the variable X is either a fixed or floating number, and a program branches to treat the two cases separately. On one path X is known to be fixed, whereas on the other it is known to be floating. The Decl package DPROGN construct can be used in such cases to state the type of the variable along each path. DPROGN is exactly like PROGN, except that the second element of the form is interpreted as a list of DLAMBDA format declarations. These declarations are added to any existing declarations in the containing scope, and the composite declaration (created using the ALLOF type expression), is considered to hold throughout the lexical scope created by the DPROGN. Thus, our example becomes:
|
||
(if (FIXP X)
|
||
then (DPROGN ((X FIXP))...else (DPROGN ((X FLOATP)) ...))
|
||
Like DPROG and DLAMBDA, the value of a DPROGN may also be declared, using the pseudo-variable RETURNS.
|
||
DPROGN may be used not only to restrict the declarations of local variables, but also to declare variables that are being used freely. For example, if the variable A is used freely inside a function but is known to be FIXP, this fact could be noted by enclosing the body of the function in (DPROGN ((A FIXP FREE)) BODY). Instead of FREE, the more specific construction (BOUNDIN FUNCTION1 FUNCTION 2. . .) can be used. This not only states that the variable is used freely but also gives the names of the functions that might have provided this binding.2
|
||
Since the DPROGN form introduces another level of parenthesization, which results in the enclosed forms being prettyprinted indented, the Decl package also permits such declarations to be attached to their enclosing DLAMBDA or DPROG scopes by placing a DEC expression, e.g., (DECL (A FIXP (BOUNDIN FUM)), before the first executable form in that scope. Like DPROGN's, DECL declarations use DLAMBDA format.
|
||
DECLARING THE VALUES OF EXPRESSIONS
|
||
The Decl package allows the value of an arbitrary form to be declared with the Decl construct THE. A THE expression is of the form (THE TYPE . FORMS), e.g., (THE FIXP (FOO X)). FORMS are evaluated in order, and the value of the last one is checked to see if it satisfies TYPE, a type name or type expression. If so, its value is returned, otherwise a declaration fault occurs.
|
||
ASSERTIONS
|
||
The Decl package also allows for checking that an arbitrary predicate holds at a particular point in a program's execution, e.g., a condition that must hold at function entry but not throughout its execution. Such predicates can be checked using an expression of the form (ASSERT FORM1 FORM2), in which each FORM1 is either a list (which will be evaluated) or a variable (whose declaration will be checked). Unless all elements of the ASSERT form are satisfied, a declaration fault will take place.
|
||
ASSERTing a variable provides a convenient way of verifying that the value of the variable has not been improperly changed by a lower function. Although a similar effect could be achieved for predicates by explicit checks of the form (OR PREDICATE (SHOULDNT)), ASSERT also provides the ability both to check that a variable's declaration is currently satisfied and to remove its checks at compile time without source code modification (see COMPILEIGNOREDECL).
|
||
USING TYPE EXPRESSIONS AS PREDICATES
|
||
The Decl package extends the Record package TYPE? construct so that it accepts decltypes, as well as record names, e.g., (TYPE? (FIXP (SATISFIES (ILESSP VALUE 0))) EXPR). Thus, a TYPE? expression is exactly the same as a THE expression except that, rather than causing a declaration fault, TYPE? is a predicate that determines whether or not the value satisfies the given type.
|
||
ENFORCEMENT
|
||
The Decl package is a <20><>soft'' typing system<65><6D> |