10. FUNCTION DEFINITION, MANIPULATION, AND EVALUATION The Interlisp programming system is designed to help the user define and debug functions. Developing an applications program in Interlisp involves defining a number of functions in terms of the system primitives and other user-defined functions. Once defined, the user's functions may be referenced exactly like Interlisp primitive functions, so the programming process can be viewed as extending the Interlisp language to include the required functionality. Functions are defined with a list expressions known as an "expr definition." An expr definition specifies if the function has a fixed or variable number of arguments, whether these arguments are evaluated or not, the function argument names, and a series of forms which define the behavior of the function. For example: (LAMBDA (X Y) (PRINT X) (PRINT Y)) A function defined with this expr definition would have two evaluated arguments, X and Y, and it would execute (PRINT X) and (PRINT Y) when evaluated. Other types of expr definitions are described below. A function is defined by putting an expr definition in the function definition cell of a litatom. There are a number of functions for accessing and setting function definition cells, but one usually defines a function with DEFINEQ (page X.XX). For example: _ (DEFINEQ (FOO (LAMBDA (X Y) (PRINT X) (PRINT Y)) (FOO) The expression above will define the function FOO to have the expr definition (LAMBDA (X Y) (PRINT X) (PRINT Y)). After being defined, this function may be evaluated just like any system function: _ (FOO 3 (IPLUS 3 4)) 3 7 7 _ All function definition cells do not contain expr definitions. The compiler (page X.XX) translates expr definitions into compiled code objects, which execute much faster. Interlisp provides a number of "function type functions" which determine how a given function is defined, the number and names of function arguments, etc. See page X.XX. Usually, functions are evaluated automatically when they appear within another function or when typed into Interlisp. However, sometimes it is useful to envoke the Interlisp interpreter explicitly to apply a given "functional argument" to some data. There are a number of functions which will apply a given function repeatedly. For example, MAPCAR will apply a function (or an expr definition) to all of the elements of a list, and return the values returned by the function: _ (MAPCAR '(1 2 3 4 5) '(LAMBDA (X) (ITIMES X X)) (1 4 9 16 25) When using functional arguments, there are a number of problems which can arise, related with accessing free variables from within a function argument. Many times these problems can be solved using the function FUNCTION to create a FUNARG object (see page X.XX). The macro facility provides another way of specifying the behavior of a function (see page X.XX). Macros are very useful when developing code which should run very quickly, which should be compiled differently than it is interpreted, or which should run differently in different implementations of Interlisp. 10.1 Function Types Interlisp functions are defined using list expressions called "expr definitions." An expr definition is a list of the form (LAMBDA-WORD ARG-LIST FORM1 ... FORMN). LAMBDA-WORD determines whether the arguments to this function will be evaluated or not, ARG-LIST determines the number and names of arguments, and FORM1 ... FORMN are a series of forms to be evaluated after the arguments are bound to the local variables in ARG-LIST. If LAMBDA-WORD is the litatom LAMBDA, then the arguments to the function are evaluated. If LAMBDA-WORD is the litatom NLAMBDA, then the arguments to the function are not evaluated. Functions which evaluate or don't evaluate their arguments are therefore known as "lambda" or "nlambda" functions, respectively. If ARG-LIST is NIL or a list of litatoms, this indicates a function with a fixed number of arguments. Each litatom is the name of an argument for the function defined by this expression. The process of binding these litatoms to the individual arguments is called "spreading" the arguments, and the function is called a "spread" function. If the argument list is any litatom other than NIL, this indicates a function with a variable number of arguments, known as a "nospread" function. If ARG-LIST is anything other than a litatom or a list of litatoms, such as (LAMBDA "FOO" ...), attempting to use this expr definition will generate an ARG NOT LITATOM error. In addition, if NIL or T is used as an argument name, the error ATTEMPT TO BIND NIL OR T is generated. These two parameters (lambda/nlambda and spread/nospread) may be specified independently, so there are four main function types, known as lambda-spread, nlambda-spread, lambda-nospread, and nlambda-nospread functions. Each one has a different form, and is used for a different purpose. These four function types are described more fully below. Note: For lambda-spread, lambda-nospread, or nlambda-spread functions, there is an upper limit to the number of arguments that a function can have, based on the number of arguments that can be stored on the stack on any one function call. Currently, the limit is 80 arguments. If a function is called with more than that many arguments, the error TOO MANY ARGUMENTS occurs. However, nlambda-nospread functions can be called with an arbitrary number of arguments, since the arguments are not individually saved on the stack (see page X.XX). 10.1.1 Lambda-Spread Functions Lambda-spread functions take a fixed number of evaluated arguments. This is the most common function type. A lambda-spread expr definition has the form: (LAMBDA (ARG1 ... ARGM) FORM1 ... FORMN) The argument list (ARG1 ... ARGM) is a list of litatoms that gives the number and names of the formal arguments to the function. If the argument list is () or NIL, this indicates that the function takes no arguments. When a lambda-spread function is applied to some arguments, the arguments are evaluated, and bound to the local variables ARG1 ... ARGM. Then, FORM1 ... FORMN are evaluated in order, and the value of the function is the value of FORMN. _ (DEFINEQ (FOO (LAMBDA (X Y) (PRINT X) (PRINT Y))) ) (FOO) _ (FOO 99 (PLUS 3 4)) 99 7 7 _ In the above example, the function FOO defined by (LAMBDA (X Y) (PRINT X) (PRINT Y)) is applied to the arguments 99 and (PLUS 3 4), these arguments are evaluated (giving 99 and 7), the local variable X is bound to 99 and Y to 7, (PRINT X) is evaluated, printing 99, (PRINT Y) is evaluated, printing 7, and 7 (the value of (PRINT Y)) is returned as the value of the function. A standard feature of the Interlisp system is that no error occurs if a spread function is called with too many or too few arguments. If a function is called with too many arguments, the extra arguments are evaluated but ignored. If a function is called with too few arguments, the unsupplied ones will be delivered as NIL. In fact, a spread function cannot distinguish between being given NIL as an argument, and not being given that argument, e.g., (FOO) and (FOO NIL) are exactly the same for spread functions. If it is necessary to distinguish between these two cases, use an nlambda function and explicitly evaluate the arguments with the EVAL function (page X.XX). 10.1.2 Nlambda-Spread Functions Nlambda-spread functions take a fixed number of unevaluated arguments. An nlambda-spread expr definition has the form: (NLAMBDA (ARG1 ... ARGM) FORM1 ... FORMN) Nlambda-spread functions are evaluated similarly to lambda-spread functions, except that the arguments are not evaluated before being bound to the variables ARG1 ... ARGM. _ (DEFINEQ (FOO (NLAMBDA (X Y) (PRINT X) (PRINT Y))) ) (FOO) _ (FOO 99 (PLUS 3 4)) 99 (PLUS 3 4) (PLUS 3 4) _ In the above example, the function FOO defined by (NLAMBDA (X Y) (PRINT X) (PRINT Y)) is applied to the arguments 99 and (PLUS 3 4), these arguments are bound unevaluated to X and Y, (PRINT X) is evaluated, printing 99, (PRINT Y) is evaluated, printing (PLUS 3 4), and the list (PLUS 3 4) is returned as the value of the function. Note: Functions can be defined so that all of their arguments are evaluated (lambda functions) or none are evaluated (nlambda functions). If it is desirable to write a function which only evaluates some of its arguments (e.g. SETQ), the function should be defined as an nlambda, with some arguments explicitly evaluated using the function EVAL (page X.XX). If this is done, the user should put the litatom EVAL on the property list of the function under the property INFO. This informs various system packages such as DWIM, CLISP, and Masterscope that this function in fact does evaluate its arguments, even though it is an nlambda. Warning: A frequent problem that occurs when evaluating arguments to nlambda functions with EVAL (page X.XX) is that the form being evaluated may reference variables that are not accessable within the nlambda function. This is usually not a problem when interpreting code, but when the code is compiled, the values of "local" variables may not be accessable on the stack (see page X.XX). The system nlambda functions that evaluate their arguments (such as SETQ) are expanded in-line by the compiler, so this is not a problem. Using the macro facility (page X.XX) is recommended in cases where it is necessary to evaluate some arguments to an nlambda function. 10.1.3 Lambda-Nospread Functions Lambda-nospread functions take a variable number of evaluated arguments. A lambda-nospread expr definition has the form: (LAMBDA VAR FORM1 ... FORMN) VAR may be any litatom, except NIL and T. When a lambda-nospread function is applied to some arguments, each of these arguments is evaluated and the values stored on the pushdown list. VAR is then bound to the number of arguments which have been evaluated. For example, if FOO is defined by (LAMBDA X ...), when (FOO A B C) is evaluated, A, B, and C are evaluated and X is bound to 3. VAR should never be reset. The following functions are used for accessing the arguments of lambda-nospread functions: (ARG VAR M) [NLambda Function] Returns the Mth argument for the lambda-nospread function whose argument list is VAR. VAR is the name of the atomic argument list to a lambda-nospread function, and is not evaluated; M is the number of the desired argument, and is evaluated. The value of ARG is undefined for M less than or equal to 0 or greater than the value of VAR. (SETARG VAR M X) [NLambda Function] Sets the Mth argument for the lambda-nospread function whose argument list is VAR to X. VAR is not evaluated; M and X are evaluated. M should be between 1 and the value of VAR. In the example below, the function FOO is defined to print all of the evaluated arguments it is given, and return NIL (the value of the for statement). _ (DEFINEQ (FOO (LAMBDA X (for ARGNUM from 1 to X do (PRINT (ARG X ARGNUM))))) ) (FOO) _ (FOO 99 (PLUS 3 4)) 99 7 NIL _ (FOO 99 (PLUS 3 4) (TIMES 3 4)) 99 7 12 NIL _ 10.1.4 Nlambda-Nospread Functions Nlambda-nospread functions take a variable number of unevaluated arguments. An nlambda-nospread expr definition has the form: (NLAMBDA VAR FORM1 ... FORMN) VAR may be any litatom, except NIL and T. Though similar in form to lambda-nospread expr definitions, an nlambda-nospread is evaluated quite differently. When an nlambda-nospread function is applied to some arguments, VAR is simply bound to a list of the unevaluated arguments. The user may pick apart this list, and evaluate different arguments. In the example below, FOO is defined to print (and then return) the reverse of list of arguments it is given (unevaluated): _ (DEFINEQ (FOO (NLAMBDA X (REVERSE X)))) (FOO) _ (FOO 99 (PLUS 3 4)) ((PLUS 3 4) 99) ((PLUS 3 4) 99) _ (FOO 99 (PLUS 3 4) (TIMES 3 4)) ((TIMES 3 4) (PLUS 3 4) 99) ((TIMES 3 4) (PLUS 3 4) 99) _ Note: The warning about evaluating arguments to nlambda functions (page X.XX) also applies to nlambda-nospread function. 10.1.5 Compiled Functions Functions defined by expr definitions can be compiled by the Interlisp compiler (page X.XX), which produces compiled code objects, which execute more quickly than the corresponding expr definition code. Functions defined by compiled code objects may have the same four types as expr definitions (lambda/nolambda, spread/nospread). Functions created by the compiler are referred to as compiled functions. 10.1.6 Function Type Functions There are a variety of functions used for examining the type, argument list, etc. of functions. These functions may be given either a litatom, in which case they obtain the function definition from the litatom's definition cell, or a function definition itself. (FNTYP FN) [Function] Returns NIL if FN is not a function definition or the name of a defined function. Otherwise FNTYP returns one of the following litatoms, depending on the type of function definition: EXPR Lambda-spread expr definition. CEXPR Lambda-spread compiled definition. FEXPR Nlambda-spread expr definition. CFEXPR Nlambda-spread compiled definition. EXPR* Lambda-nospread expr definition. CEXPR* Lambda-nospread compiled definition. FEXPR* Nlambda-nospread expr definition. CFEXPR* Nlambda-nospread compiled definition. FUNARG FNTYP returns the litatom FUNARG if FN is a FUNARG expression. See page X.XX. EXPR, FEXPR, EXPR*, and FEXPR* indicate that FN is defined by an expr definition. CEXPR, CFEXPR, CEXPR*, and CFEXPR* indicate that FN is defined by a compiled definition, as indicated by the prefix C. The suffix * indicates that FN has an indefinite number of arguments, i.e., is a nospread functions. The prefix F indicates unevaluated arguments. Thus, for example, a CFEXPR* is a compiled nospread-nlambda function. (EXPRP FN) [Function] Returns T if (FNTYP FN) is either EXPR, FEXPR, EXPR*, or FEXPR*; NIL otherwise. However, (EXPRP FN) is also true if FN is (has) a list definition, even if it does not begin with LAMBDA or NLAMBDA. In other words, EXPRP is not quite as selective as FNTYP. (CCODEP FN) [Function] Returns T if (FNTYP FN) is either CEXPR, CFEXPR, CEXPR*, or CFEXPR*; NIL otherwise. (ARGTYPE FN) [Function] FN is the name of a function or its definition. ARGTYPE returns 0, 1, 2, or 3, or NIL if FN is not a function. The interpretation of this value is: 0 Lambda-spread function (EXPR, CEXPR) 1 Nlambda-spread function (FEXPR, CFEXPR) 2 Lambda-nospread function (EXPR*, CEXPR*) 3 Nlambda-nospread function (FEXPR*, CFEXPR*) i.e., ARGTYPE corresponds to the rows of FNTYP's. (NARGS FN) [Function] Returns the number of arguments of FN, or NIL if FN is not a function. If FN is a nospread function, the value of NARGS is 1. (ARGLIST FN) [Function] Returns the "argument list" for FN. Note that the "argument list" is a litatom for nospread functions. Since NIL is a possible value for ARGLIST, an error is generated, ARGS NOT AVAILABLE, if FN is not a function. If FN is a compiled function, the argument list is constructed, i.e., each call to ARGLIST requires making a new list. For functions defined by expr definitions, lists beginning with LAMBDA or NLAMBDA, the argument list is simply CADR of GETD. If FN has an expr definition, and CAR of the definition is not LAMBDA or NLAMBDA, ARGLIST will check to see if CAR of the definition is a member of LAMBDASPLST (page X.XX). If it is, ARGLIST presumes this is a function object the user is defining via DWIMUSERFORMS (page X.XX), and simply returns CADR of the definition as its argument list. Otherwise ARGLIST generates an error as described above. (SMARTARGLIST FN EXPLAINFLG TAIL) [Function] A "smart" version of ARGLIST that tries various strategies to get the arglist of FN. SMARTARGLIST is used by BREAK (page X.XX) and ADVISE (page X.XX) with EXPLAINFLG=NIL for constructing equivalent expr definitions, and by the programmer's assistant command ?= (page X.XX), with EXPLAINFLG=T. In order to provide the user with an override, SMARTARGLIST first checks the property list of FN under the property ARGNAMES. For spread functions, the argument list itself is stored. For nospread functions, the form is (NIL ARGLIST1 . ARGLIST2) where ARGLIST1 is the value of SMARTARGLIST when EXPLAINFLG=T, and ARGLIST2 the value when EXPLAINFLG=NIL. For example, (GETPROP 'DEFINEQ 'ARGNAMES) = (NIL (X1 XI ... XN) . X). Secondly, if FN is not defined as a function, SMARTARGLIST attempts spelling correction on FN by calling FNCHECK (page X.XX), passing TAIL to be used for the call to FIXSPELL. If unsuccessful, an error will be generated, FN NOT A FUNCTION. If FN is known to the file package (page X.XX) but not loaded in, SMARTARGLIST will obtain the arglist information from the file. For all other cases, SMARTARGLIST simply returns (ARGLIST FN). 10.2 Function Definition Function definitions are stored in a "function definition cell" associated with each litatom. This cell is directly accessible via the two functions PUTD and GETD (page X.XX), but it is usually easier to define functions with DEFINEQ: (DEFINEQ X1 X2 ... XN) [NLambda NoSpread Function] DEFINEQ is the function normally used for defining functions. It takes an indefinite number of arguments which are not evaluated. Each Xi must be a list defining one function, of the form (NAME DEFINITION). For example: (DEFINEQ (DOUBLE (LAMBDA (X) (IPLUS X X))) ) The above expression will define the function DOUBLE with the expr definition (LAMBDA (X) (IPLUS X X)). Xi may also have the form (NAME ARGS . DEF-BODY), in which case an appropriate lambda expr definition will be constructed. Therefore, the above expression is exactly the same as: (DEFINEQ (DOUBLE (X) (IPLUS X X)) ) Note that this alternate form can only be used for Lambda functions. The first form must be used to define an nlambda function. DEFINEQ returns a list of the names of the functions defined. (DEFINE X ---) [Function] Lambda-spread version of DEFINEQ. Each element of the list X is itself a list either of the form (NAME DEFINITION) or (NAME ARGS . DEF-BODY). DEFINE will generate an error, INCORRECT DEFINING FORM, on encountering an atom where a defining list is expected. Note: DEFINE and DEFINEQ will operate correctly if the function is already defined and BROKEN, ADVISED, or BROKEN-IN. For expressions involving type-in only, if the time stamp facility is enabled (page X.XX), both DEFINE and DEFINEQ will stamp the definition with the user's initials and date. UNSAFE.TO.MODIFY.FNS [Variable] Value is a list of functions that should not be redefined, because doing so may cause unusual bugs (or crash the system!). If the user tries to modify a function on this list (using DEFINEQ, TRACE, etc), the system will print "Warning: XXX may be safe to modify -- continue?" If the users types "Yes", the function is modified, otherwise an error occurs. This provides a measure of safety for novices who may accidently redefine important system functions. Users can add their own functions onto this list. Note: By convention, all functions starting with the character backslash ("\") are system internal functions, which should never be redefined or modified by the user. Backslash functions are not on UNSAFE.TO.MODIFY.FNS, so trying to redefine them will not cause a warning. DFNFLG [Variable] DFNFLG is a global variable that effects the operation of DEFINE (and DEFINEQ, which calls DEFINE). If DFNFLG=NIL, an attempt to redefine a function FN will cause DEFINE to print the message (FN REDEFINED) and to save the old definition of FN using SAVEDEF before redefining it, except if the old and new definitions are the same (i.e. EQUAL), the effect is simply a no-op. If DFNFLG=T, the function is simply redefined. If DFNFLG=PROP or ALLPROP, the new definition is stored on the property list under the property EXPR. ALLPROP affects the operation of RPAQQ and RPAQ (page X.XX). DFNFLG is initially NIL. DFNFLG is reset by LOAD (page X.XX) to enable various ways of handling the defining of functions and setting of variables when loading a file. For most applications, the user will not reset DFNFLG directly. Note: The compiler does NOT respect the value of DFNFLG when it redefines functions to their compiled definitions (see page X.XX). Therefore, if you set DFNFLG to PROP to completely avoid inadvertantly redefining something in your running system, you MUST use compile mode F, not ST. (SAVEDEF FN --- ---) [Function] Saves the definition of FN on its property list under the property EXPR, or CODE depending on its FNTYP. Returns the property name used. If (GETD FN) is non-NIL, but (FNTYP FN)=NIL, SAVEDEF saves the definition on the property name LIST. This situation can arise when a function is redefined which was originally defined with LAMBDA misspelled or omitted. If FN is a list, SAVEDEF operates on each function in the list, and returns a list of the individual values. (UNSAVEDEF FN PROP ---) [Function] Restores the definition of FN from its property list under property PROP (see SAVEDEF above). Returns PROP. If nothing is saved under PROP, and FN is defined, returns (PROP NOT FOUND), otherwise generates an error, NOT A FUNCTION. If PROP is not given, i.e., NIL, UNSAVEDEF looks under the properties EXPR, and CODE, in that order. The value of UNSAVEDEF is the property name, or if nothing is found and FN is a function, the value is (NOTHING FOUND); otherwise generates an error, NOT A FUNCTION. If DFNFLG=NIL, the current definition of FN, if any, is saved using SAVEDEF. Thus one can use UNSAVEDEF to switch back and forth between two definitions of the same function, keeping one on its property list and the other in the function definition cell. If FN is a list, UNSAVEDEF operates on each function of the list, and its value is a list of the individual values. Both SAVEDEF and UNSAVEDEF are redefined in more general terms (see page X.XX) to operate on typed definitions of which a function definition is but one example. Thus, their actual argument lists in Interlisp are different than given here. However, when their extra arguments are defaulted to NIL, they operate as described above. (GETD FN) [Function] Returns the function definition of FN. Returns NIL if FN is not a litatom, or has no definition. GETD of a compiled function constructs a pointer to the definition, with the result that two successive calls do not necessarily produce EQ results. EQP or EQUAL must be used to compare compiled definitions. (PUTD FN DEF ---) [Function] Puts DEF into FN's function cell, and returns DEF. Generates an error, ARG NOT LITATOM, if FN is not a litatom. Generates an error, ILLEGAL ARG, if DEF is a string, number, or a litatom other than NIL. (MOVD FROM TO COPYFLG ---) [Function] Moves the definition of FROM to TO, i.e., redefines TO. If COPYFLG=T, a COPY of the definition of FROM is used. COPYFLG=T is only meaningful for expr definitions, although MOVD works for compiled functions as well. MOVD returns TO. COPYDEF (page X.XX) is a higher-level function that only moves expr definitions, but works also for variables, records, etc. (MOVD? FROM TO COPYFLG ---) [Function] If TO is not defined, same as (MOVD FROM TO COPYFLG). Otherwise, does nothing and returns NIL. 10.3 Function Evaluation Usually, function application is done automatically by the Interlisp interpreter. If a form is typed into Interlisp whose CAR is a function, this function is applied to the arguments in the CDR of the form. These arguments are evaluated or not, and bound to the function parameters, as determined by the type of the function, and the body of the function is evaluated. This sequence is repeated as each form in the body of the function is evaluated. There are some situations where it is necessary to explicitly call the evaluator, and Interlisp supplies a number of functions that will do this. These functions take "functional arguments", which may either be litatoms with function definitions, or expr definition forms such as (LAMBDA (X) ...), or FUNARG expressions (see page X.XX). The following functions are useful when one wants to supply a functional argument which will always return NIL, T, or 0. Note that the arguments X1 ... XN to these functions are evaluated, though they are not used. (NILL X1 ... XN) [NoSpread Function] Returns NIL. (TRUE X1 ... XN) [NoSpread Function] Returns T. (ZERO X1 ... XN) [NoSpread Function] Returns 0. Note: When using expr definitions as functional arguments, they should be enclosed within the function FUNCTION (page X.XX) rather than QUOTE, so that they will be compiled as separate functions. FUNCTION can also be used to create FUNARG expressions, which can be used to solve some problems with referencing free variables, or to create functional arguments which carry "state" along with them. (EVAL X ---) [Function] EVAL evaluates the expression X and returns this value, i.e., EVAL provides a way of calling the Interlisp interpreter. Note that EVAL is itself a lambda function, so its argument is first evaluated, e.g., _(SETQ FOO '(ADD1 3)) (ADD1 3) _(EVAL FOO) 4 _(EVAL 'FOO) (ADD1 3) (EVALHOOK FORM EVALHOOKFN) [Function] EVALHOOK evaluates the expression FORM, and returns its value. While evaluating FORM, the function EVAL behaves in a special way. Whenever a list other than FORM itself is to be evaluated, whether implicitly or via an explicit call to EVAL, EVALHOOKFN is invoked (it should be a function), with the form to be evaluated as its argument. EVALHOOKFN is then responsible for evaluating the form; whatever is returned is assumed to be the result of evaluating the form. During the execution of EVALHOOKFN, this special evaluation is turned off. (Note that EVALHOOK does not effect the evaluations of variables, only of lists). Here is an example of a simple tracing routine that uses the EVALHOOK feature: _(DEFINEQ (PRINTHOOK (FORM) (printout T "eval: " FORM T) (EVALHOOK FORM (FUNCTION PRINTHOOK] (PRINTHOOK) Using PRINTHOOK, one might see the following interaction: _(EVALHOOK '(LIST (CONS 1 2) (CONS 3 4)) 'PRINTHOOK) eval: (CONS 1 2) eval: (CONS 3 4) ((1 . 2) (3 . 4)) (QUOTE X) [NLambda NoSpread Function] Interlisp functions can either evaluate or not evaluate arguments to functions. For those cases where it is desirable to specify arguments unevaluated, one may use the QUOTE function. QUOTE prevents its arguments from being evaluated. Its value is X itself, e.g., (QUOTE FOO) is FOO. Note: Since giving QUOTE more than one argument is almost always a parentheses error, and one that would otherwise go undetected, QUOTE itself generates an error in this case, PARENTHESIS ERROR. (KWOTE X) [Function] Value is an expression which when evaluated yields X. If X is NIL or a number, this is X itself. Otherwise, (LIST (QUOTE QUOTE) X). For example, if the value of X is A and the value of Y is B, then (KWOTE (CONS X Y)) = (QUOTE (A . B)). (NLAMBDA.ARGS X) [Function] This function interprets its argument as a list of unevaluated Nlambda arguments. If any of the elements in this list are of the form (QUOTE ...), the enclosing QUOTE is stripped off. Actually, NLAMBDA.ARGS stops processing the list after the first non-quoted argument. Therefore, whereas (NLAMBDA.ARGS '((QUOTE FOO) BAR)) -> (FOO BAR), (NLAMBDA.ARGS '(FOO (QUOTE BAR))) -> (FOO (QUOTE BAR)). NLAMBDA.ARGS is called by a number of nlambda functions in the system, to interpret their arguments. For instance, the function BREAK calls NLAMBDA.ARGS so that (BREAK 'FOO) will break the function FOO, rather than the function QUOTE. (DEFEVAL TYPE FN) [Function] Specifies how a datum of a particular type is to be evaluated. Intended primarily for user defined data types, but works for all data types except lists, literal atoms, and numbers. TYPE is a type name. FN is a function object, i.e. name of a function or a lambda expression. Whenever the interpreter encounters a datum of the indicated type, FN is applied to the datum and its value returned as the result of the evaluation. DEFEVAL returns the previous evaling function for this type. If FN=NIL, DEFEVAL returns the current evaling function without changing it. If FN=T, the evaling function is set back to the system default (which for all data types except lists is to return the datum itself). Note: COMPILETYPELST (page X.XX) permits the user to specify how a datum of a particular type is to be compiled. (APPLY FN ARGLIST ---) [Function] Applies the function FN to the arguments in the list ARGLIST, and returns its value. APPLY is a lambda function, so its arguments are evaluated, but the individual elements of ARGLIST are not evaluated. Therefore, lambda and nlambda functions are treated the same by APPLY; lambda functions take their arguments from ARGLIST without evaluating them. Note that FN may still explicitly evaluate one or more of its arguments itself, as SETQ does. Thus, (APPLY 'SETQ '(FOO (ADD1 3))) will set FOO to 4, whereas (APPLY 'SET '(FOO (ADD1 3))) will set FOO to the expression (ADD1 3). APPLY can be used for manipulating expr definitions, for example: _(APPLY '(LAMBDA (X Y) (ITIMES X Y)) '(3 4)) 12 (APPLY* FN ARG1 ARG2 ... ARGN) [NoSpread Function] Nospread version of APPLY, equivalent to (APPLY FN (LIST ARG1 ARG2 ... ARGN)). (EVALA X A) [Function] Simulates a-list evaluation as in LISP 1.5. X is a form, A is a list of the form: ( (NAME1 . VAL1) (NAME2 . VAL2) ... (NAMEN . VALN) ) The variable names and values in A are "spread" on the stack, and then X is evaluated. Therefore, any variables appearing free in X, that also appears as CAR of an element of A will be given the value in the CDR of that element. The functions below are used to evaluate a form or apply a function repeatedly. RPT, RPTQ, and FRPTQ evaluate a given form a specified number of times. MAP, MAPCAR, MAPLIST, etc. apply a given function repeatedly to different elements of a list, possibly constructing another list. These functions allow efficient iterative computations, but they are difficult to use. For programming iterative computations, it is usually better to use the CLISP Iterative Statement facility (page X.XX), which provides a more general and complete facility for expressing iterative statements. Whenever possible, CLISP translates iterative statements into expressions using the functions below, so there is no efficiency loss. (RPT N FORM) [Function] Evaluates the expression FORM, N times. Returns the value of the last evaluation. If N <= 0, FORM is not evaluated, and RPT returns NIL. Before each evaluation, the local variable RPTN is bound to the number of evaluations yet to take place. This variable can be referenced within FORM. For example, (RPT 10 '(PRINT RPTN)) will print the numbers 10, 9, ... 1, and return 1. (RPTQ N FORM1 FORM2 ... FORMN) [NLambda NoSpread Function] Nlambda-nospread version of RPT: N is evaluated, FORMi are not. Returns the value of the last evaluation of FORMN. (FRPTQ N FORM1 FORM2 ... FORMN) [NLambda NoSpread Function] Faster version of RPTQ. Does not bind RPTN. (MAP MAPX MAPFN1 MAPFN2) [Function] If MAPFN2 is NIL, MAP applies the function MAPFN1 to successive tails of the list MAPX. That is, first it computes (MAPFN1 MAPX), and then (MAPFN1 (CDR MAPX)), etc., until MAPX becomes a non-list. If MAPFN2 is provided, (MAPFN2 MAPX) is used instead of (CDR MAPX) for the next call for MAPFN1, e.g., if MAPFN2 were CDDR, alternate elements of the list would be skipped. MAP returns NIL. (MAPC MAPX MAPFN1 MAPFN2) [Function] Identical to MAP, except that (MAPFN1 (CAR MAPX)) is computed at each iteration instead of (MAPFN1 MAPX), i.e., MAPC works on elements, MAP on tails. MAPC returns NIL. (MAPLIST MAPX MAPFN1 MAPFN2) [Function] Successively computes the same values that MAP would compute, and returns a list consisting of those values. (MAPCAR MAPX MAPFN1 MAPFN2) [Function] Computes the same values that MAPC would compute, and returns a list consisting of those values, e.g., (MAPCAR X 'FNTYP) is a list of FNTYPs for each element on X. (MAPCON MAPX MAPFN1 MAPFN2) [Function] Computes the same values as MAP and MAPLIST but NCONCs these values to form a list which it returns. (MAPCONC MAPX MAPFN1 MAPFN2) [Function] Computes the same values as MAPC and MAPCAR, but NCONCs the values to form a list which it returns. Note that MAPCAR creates a new list which is a mapping of the old list in that each element of the new list is the result of applying a function to the corresponding element on the original list. MAPCONC is used when there are a variable number of elements (including none) to be inserted at each iteration. Examples: (MAPCONC '(A B C NIL D NIL) '(LAMBDA (Y) (if (NULL Y) then NIL else (LIST Y)))) ==> (A B C D) This MAPCONC returns a list consisting of MAPX with all NILs removed. (MAPCONC '((A B) C (D E F) (G) H I) '(LAMBDA (Y) (if (LISTP Y) then Y else NIL))) ==> (A B D E F G) This MAPCONC returns a linear list consisting of all the lists on MAPX. Since MAPCONC uses NCONC to string the corresponding lists together, in this example the original list will be altered to be ((A B D E F G) C (D E F G) (G) H I). If this is an undesirable side effect, the functional argument to MAPCONC should return instead a top level copy of the lists, i.e. (LAMBDA (Y) (if (LISTP Y) then (APPEND Y) else NIL))). (MAP2C MAPX MAPY MAPFN1 MAPFN2) [Function] Identical to MAPC except MAPFN1 is a function of two arguments, and (MAPFN1 (CAR MAPX) (CAR MAPY)) is computed at each iteration. Terminates when either MAPX or MAPY is a non-list. MAPFN2 is still a function of one argument, and is applied twice on each iteration; (MAPFN2 MAPX) gives the new MAPX, (MAPFN2 MAPY) the new MAPY. CDR is used if MAPFN2 is not supplied, i.e., is NIL. (MAP2CAR MAPX MAPY MAPFN1 MAPFN2) [Function] Identical to MAPCAR except MAPFN1 is a function of two arguments and (MAPFN1 (CAR MAPX) (CAR MAPY)) is used to assemble the new list. Terminates when either MAPX or MAPY is a non-list. (SUBSET MAPX MAPFN1 MAPFN2) [Function] Applies MAPFN1 to elements of MAPX and returns a list of those elements for which this application is non-NIL, e.g., (SUBSET '(A B 3 C 4) 'NUMBERP) = (3 4). MAPFN2 plays the same role as with MAP, MAPC, et al. (EVERY EVERYX EVERYFN1 EVERYFN2) [Function] Returns T if the result of applying EVERYFN1 to each element in EVERYX is true, otherwise NIL. For example, (EVERY '(X Y Z) 'ATOM) => T. EVERY operates by evaluating (EVERYFN1 (CAR EVERYX) EVERYX). The second argument is passed to EVERYFN1 so that it can look at the next element on EVERYX if necessary. If EVERYFN1 yields NIL, EVERY immediately returns NIL. Otherwise, EVERY computes (EVERYFN2 EVERYX), or (CDR EVERYX) if EVERYFN2=NIL, and uses this as the "new" EVERYX, and the process continues. For example, (EVERY X 'ATOM 'CDDR) is true if every other element of X is atomic. (SOME SOMEX SOMEFN1 SOMEFN2) [Function] Returns the tail of SOMEX beginning with the first element that satisfies SOMEFN1, i.e., for which SOMEFN1 applied to that element is true. Value is NIL if no such element exists. (SOME X '(LAMBDA (Z) (EQUAL Z Y))) is equivalent to (MEMBER Y X). SOME operates analogously to EVERY. At each stage, (SOMEFN1 (CAR SOMEX) SOMEX) is computed, and if this is not NIL, SOMEX is returned as the value of SOME. Otherwise, (SOMEFN2 SOMEX) is computed, or (CDR SOMEX) if SOMEFN2=NIL, and used for the next SOMEX. (NOTANY SOMEX SOMEFN1 SOMEFN2) [Function] (NOT (SOME SOMEX SOMEFN1 SOMEFN2)) (NOTEVERY EVERYX EVERYFN1 EVERYFN2) [Function] (NOT (EVERY EVERYX EVERYFN1 EVERYFN2)) (MAPRINT LST FILE LEFT RIGHT SEP PFN LISPXPRINTFLG) [Function] A general printing function. It cycles through LST applying PFN (or PRIN1 if PFN not given) to each element of LST. Between each application, MAPRINT performs PRIN1 of SEP (or " " if SEP=NIL). If LEFT is given, it is printed (using PRIN1) initially; if RIGHT is given it is printed (using PRIN1) at the end. For example, (MAPRINT X NIL '%( '%)) is equivalent to PRIN1 for lists. To print a list with commas between each element and a final "." one could use (MAPRINT X T NIL '%. '%,). If LISPXPRINTFLG=T, LISPXPRIN1 (page X.XX) is used instead of PRIN1. 10.4 Functional Arguments When using functional arguments, the following function is very useful: (FUNCTION FN ENV) [NLambda Function] If ENV=NIL, FUNCTION is the same as QUOTE, except that it is treated differently when compiled. Consider the function definition: (DEFINEQ (FOO ... (FIE LST (FUNCTION (LAMBDA (Z) (ITIMES Z Z)))) ) ) FOO calls the function FIE with the value of LST and the expr definition (LAMBDA (Z) (LIST (CAR Z))). If FOO is run interpreted, it doesn't make any difference whether FUNCTION or QUOTE is used. However, when FOO is compiled, if FUNCTION is used the compiler will define and compile the expr definition as an auxiliary function (See page X.XX). The compiled expr definition will run considerably faster, which can make a big difference if it is applied repeatedly. Note: Compiling FUNCTION will not create an auxiliary function if it is a functional argument to a function that compiles open, such as most of the mapping functions (MAPCAR, MAPLIST, etc.). If ENV is not NIL, it can be a list of variables that are (presumably) used freely by FN. In this case, the value of FUNCTION is an expression of the form (FUNARG FN POS), where POS is a stack pointer to a frame that contains the variable bindings for those variables on ENV. ENV can also be a stack pointer itself, in which case the value of FUNCTION is (FUNARG FN ENV). Finally, ENV can be an atom, in which case it is evaluated, and the value interpreted as described above. As explained above, one of the possible values that FUNCTION can return is the form (FUNARG FN POS), where FN is a function and POS is a stack pointer. FUNARG is not a function itself. Like LAMBDA and NLAMBDA, it has meaning and is specially recognized by Interlisp only in the context of applying a function to arguments. In other words, the expression (FUNARG FN POS) is used exactly like a function. When a FUNARG expression is applied or is CAR of a form being EVAL'ed, the APPLY or EVAL takes place in the access environment specified by ENV (see page X.XX). Consider the following example: _ (DEFINEQ (DO.TWICE (FN VAL) (APPLY* FN (APPLY* FN VAL))) ) (DO.TWICE) _ (DO.TWICE [FUNCTION (LAMBDA (X) (IPLUS X X))] 5) 20 _ (SETQ VAL 1) 1 _ (DO.TWICE [FUNCTION (LAMBDA (X) (IPLUS X VAL))] 5) 20 _ (DO.TWICE [FUNCTION (LAMBDA (X) (IPLUS X VAL)) (VAL)] 5) 7 DO.TWICE is defined to apply a function FN to a value VAL, and apply FN again to the value returned; in other words it calculates (FN (FN VAL)). Given the expr definition (LAMBDA (X) (IPLUS X X)), which doubles a given value, it correctly calculates (FN (FN 5)) = (FN 10) = 20. However, when given (LAMBDA (X) (IPLUS X VAL)), which should add the value of the global variable VAL to the argument X, it does something unexpected, returning 20 again, rather than 5+1+1 = 7. The problem is that when the expr definition is evaluated, it is evaluated in the context of DO.TWICE, where VAL is bound to the second argument of DO.TWICE, namely 5. In this case, one solution is to use the ENV argument to FUNCTION to construct a FUNARG expression which contains the value of VAL at the time that the FUNCTION is executed. Now, when (LAMBDA (X) (IPLUS X VAL)) is evaluated, it is evaluated in an environment where the global value of VAL is accessable. Admittedly, this is a somewhat contrived example (it would be easy enough to change the argument names to DO.TWICE so there would be no conflict), but this situation arises occasionally with large systems of programs that construct functions, and pass them around. Note: System functions with functional arguments (APPLY, MAPCAR, etc.) are compiled so that their arguments are local, and not accessable (see page X.XX). This reduces problems with conflicts with free variables used in functional arguments. FUNARG expressions can be used for more than just circumventing the clashing of variables. For example, a FUNARG expression can be returned as the value of a computation, and then used "higher up". Furthermore, if the function in a FUNARG expression sets any of the variables contained in the frame, only the frame would be changed. For example, consider the following function: (MAKECOUNTER (CNT) (FUNCTION [LAMBDA NIL (PROG1 CNT (SETQ CNT (ADD1 CNT] (CNT))) The function MAKECOUNTER returns a FUNARG that increments and returns the previous value of the counter CNT. However, this is done within the environment of the call to MAKECOUNTER where FUNCTION was executed, which the FUNARG expression "carries around" with it, even after MAKECOUNTER has finished executing. Note that each call to MAKECOUNTER creates a FUNARG expression with a new, independent environment, so that multiple counters can be generated and used: _ (SETQ C1 (MAKECOUNTER 1)) (FUNARG (LAMBDA NIL (PROG1 CNT (SETQ CNT (ADD1 CNT)))) #1,13724/*FUNARG) _ (APPLY C1) 1 _ (APPLY C1) 2 _ (SETQ C2 (MAKECOUNTER 17)) (FUNARG (LAMBDA NIL (PROG1 CNT (SETQ CNT (ADD1 CNT)))) #1,13736/*FUNARG) _ (APPLY C2) 17 _ (APPLY C2) 18 _ (APPLY C1) 3 _ (APPLY C2) 19 By creating a FUNARG expression with FUNCTION, a program can create a function object which has updateable binding(s) associated with the object which last between calls to it, but are only accessible through that instance of the function. For example, using the FUNARG device, a program could maintain two different instances of the same random number generator in different states, and run them independently. 10.5 Macros Macros provide an alternative way of specifying the action of a function. Whereas function definitions are evaluated with a "function call", which involves binding variables and other housekeeping tasks, macros are evaluated by translating one Interlisp form into another, which is then evaluated. A litatom may have both a function definition and a macro definition. When a form is evaluated by the interpreter, if the CAR has a function definition, it is used (with a function call), otherwise if it has a macro definition, then that is used. However, when a form is compiled, the CAR is checked for a macro definition first, and only if there isn't one is the function definition compiled. This allows functions that behave differently when compiled and interpreted. For example, it is possible to define a function that, when interpreted, has a function definition that is slow and has a lot of error checks, for use when debugging a system. This function could also have a macro definition that defines a fast version of the function, which is used when the debugged system is compiled. Macro definitions are represented by lists that are stored on the property list of a litatom. Macros are often used for functions that should be compiled differently in different Interlisp implementations, and the exact property name a macro definition is stored under determines whether it should be used in a particular implementation. The global variable MACROPROPS contains a list of all possible macro property names which should be saved by the MACROS file package command. Typical macro property names are DMACRO for Interlisp-D, 10MACRO for Interlisp-10, VAXMACRO for Interlisp-VAX, JMACRO for Interlisp-Jerico, and MACRO for "implementation independent" macros. The global variable COMPILERMACROPROPS is a list of macro property names. Interlisp determines whether a litatom has a macro definition by checking these property names, in order, and using the first non-NIL property value as the macro definition. In Interlisp-D this list contains DMACRO and MACRO in that order so that DMACROs will override the implementation-independent MACRO properties. In general, use a DMACRO property for macros that are to be used only in Interlisp-D, use 10MACRO for macros that are to be used only in Interlisp-10, and use MACRO for macros that are to affect both systems. Macro definitions can take the following forms: (LAMBDA ...) (NLAMBDA ...) A function can be made to compile open by giving it a macro definition of the form (LAMBDA ...) or (NLAMBDA ...), e.g., (LAMBDA (X) (COND ((GREATERP X 0) X) (T (MINUS X)))) for ABS. The effect is as if the macro definition were written in place of the function wherever it appears in a function being compiled, i.e., it compiles as a lambda or nlambda expression. This saves the time necessary to call the function at the price of more compiled code generated in-line. (NIL EXPRESSION) (LIST EXPRESSION) "Substitution" macro. Each argument in the form being evaluated or compiled is substituted for the corresponding atom in LIST, and the result of the substitution is used instead of the form. For example, if the macro definition of ADD1 is ((X) (IPLUS X 1)), then, (ADD1 (CAR Y)) is compiled as (IPLUS (CAR Y) 1). Note that ABS could be defined by the substitution macro ((X) (COND ((GREATERP X 0) X) (T (MINUS X)))). In this case, however, (ABS (FOO X)) would compile as (COND ((GREATERP (FOO X) 0) (FOO X)) (T (MINUS (FOO X)))) and (FOO X) would be evaluated two times. (Code to evaluate (FOO X) would be generated three times.) (OPENLAMBDA ARGS BODY) This is a cross between substitution and LAMBDA macros. When the compiler processes an OPENLAMBDA, it attempts to substitute the actual arguments for the formals wherever this preserves the frequency and order of evaluation that would have resulted from a LAMBDA expression, and produces a LAMBDA binding only for those that require it. Note: OPENLAMBDA assumes that it can substitute literally the actual arguments for the formal arguments in the body of the macro if the actual is side-effect free or a constant. Thus, you should be careful to use names in ARGS which don't occur in BODY (except as variable references). For example, if FOO has a macro definition of (OPENLAMBDA (ENV) (FETCH (MY-RECORD-TYPE ENV) OF BAR)) then (FOO NIL) will expand to (FETCH (MY-RECORD-TYPE NIL) OF BAR) T When a macro definition is the atom T, it means that the compiler should ignore the macro, and compile the function definition; this is a simple way of turning off other macros. For example, the user may have a function that runs in both Interlisp-D and Interlisp-10, but has a macro definition that should only be used when compiling in Interlisp-10. If the MACRO property has the macro specification, a DMACRO of T will cause it to be ignored by the Interlisp-D compiler. Note that this DMACRO would not be necessary if the macro were specified by a 10MACRO instead of a MACRO. (= . OTHER-FUNCTION) A simple way to tell the compiler to compile one function exactly as it would compile another. For example, when compiling in Interlisp-D, FRPLACAs are treated as RPLACAs. This is achieved by having FRPLACA have a DMACRO of (= . RPLACA). (LITATOM EXPRESSION) If a macro definition begins with a litatom other than those given above, this allows computation of the Interlisp expression to be evaluated or compiled in place of the form. LITATOM is bound to the CDR of the calling form, EXPRESSION is evaluated, and the result of this evaluation is evaluated or compiled in place of the form. For example, LIST could be compiled using the computed macro: [X (LIST 'CONS (CAR X) (AND (CDR X) (CONS 'LIST (CDR X] This would cause (LIST X Y Z) to compile as (CONS X (CONS Y (CONS Z NIL))). Note the recursion in the macro expansion. If the result of the evaluation is the litatom IGNOREMACRO, the macro is ignored and the compilation of the expression proceeds as if there were no macro definition. If the litatom in question is normally treated specially by the compiler (CAR, CDR, COND, AND, etc.), and also has a macro, if the macro expansion returns IGNOREMACRO, the litatom will still be treated specially. In Interlisp-10, if the result of the evaluation is the atom INSTRUCTIONS, no code will be generated by the compiler. It is then assumed the evaluation was done for effect and the necessary code, if any, has been added. This is a way of giving direct instructions to the compiler if you understand it. Note: It is often useful, when constructing complex macro expressions, to use the BQUOTE facility (see page X.XX). The following function is quite useful for debugging macro definitions: (EXPANDMACRO EXP QUIETFLG --- ---) [Function] Takes a form whose CAR has a macro definition and expands the form as it would be compiled. The result is prettyprinted, unless QUIETFLG=T, in which case the result is simply returned. 10.5.1 Interpreting Macros When the interpreter encounters a form CAR of which is an undefined function, it tries interpreting it as a macro. If CAR of the form has a macro definition, the macro is expanded, and the result of this expansion is evaluated in place of the original form. CLISPTRAN (page X.XX) is used to save the result of this expansion so that the expansion only has to be done once. On subsequent occasions, the translation (expansion) is retrieved from CLISPARRAY the same as for other CLISP constructs. Note: Because of the way that the evaluator processes macros, if you have a macro on FOO, then typing (FOO 'A 'B) will work, but FOO(A B) will not work. Sometimes, macros contain calls to functions that assume that the macro is being compiled. The variable SHOULDCOMPILEMACROATOMS is a list of functions that should be compiled to work correctly (initially (OPCODES) in Interlisp-D, (ASSEMBLE LOC) in Interlisp-10). UNSAFEMACROATOMS is a list of functions which effect the operation of the compiler, so such macro forms shouldn't even be expanded except by the compiler (initially NIL in Interlisp-D, (C2EXP STORIN CEXP COMP) in Interlisp-10). If the interpreter encounters a macro containing calls to functions on these two lists, instead of the macro being expanded, a dummy function is created with the form as its definition, and the dummy function is then compiled. A form consisting of a call to this dummy function with no arguments is then evaluated in place of the original form, and CLISPTRAN is used to save the translation as described above. There are some situations for which this procedure is not amenable, e.g. a GO inside the form which is being compiled will cause the compiler to give an UNDEFINED TAG error message because it is not compiling the entire function, just a part of it. Copyright (c) 1985 Xerox Corporation. All rights reserved.