REDUCE

8.1 Function Definition in PSL

Functions in PSL are global entities. To avoid function-variable naming clashes, the Standard LISP Report required that no variable have the same name as a function. There is no conflict in PSL, as separate function cells and value cells are used.

The first major section in this chapter describes how to define new functions; the second describes the binding of variables in PSL. The final section presents binding functions useful in building new interpreter functions.

8.1.1 Function Types

Eval-type functions are those called with evaluated arguments. NoEval functions are called with unevaluated arguments. Spread-type functions have their arguments passed in a one-to-one correspondence with their formal parameters. NoSpread functions receive their arguments as a single list.

There are four function types implemented in PSL:

expr

An Eval, Spread function, with a maximum number of arguments. The maximum depends upon the implementation. In referring to the formal parameters we mean their values. Each function of this type should always be called with the expected number of parameters, as indicated in the function definition.

fexpr

A NoEval, NoSpread function. There is no limit on the number of arguments. In referring to the formal parameters we mean the unevaluated arguments, collected as a single list, and passed as a single formal parameter to the function body.

nexpr

An Eval, NoSpread function. Each call on this kind of function may present a different number of arguments, which are evaluated, collected into a list, and passed in to the function body as a single formal parameter.

macro

The macro is a function which creates a new S-expression for subsequent evaluation or compilation. There is no limit to the number of arguments a macro may have. The descriptions of the eval and expand functions in Chapter 11 provide precise details.

8.1.2 Notes on Code Pointers

A code-pointer may be displayed by the print functions or expanded by explode. The value appears in the convention of the implementation e.g. (#<Code:A N>, where A is the number of arguments of the function, and N is the function’s entry point). A code-pointer may not be created by compress. (See Chapter 12 for descriptions of explode and compress.) The code-pointer associated with a compiled function may be retrieved by getd and is valid as long as PSL is in execution. Normally a code-pointer is stored using putd. It may be checked for equivalence by eq. The value may be checked for being a code-pointer by the codep function.

8.1.3 Functions Useful in Function Definition

In PSL, ids have a function cell that usually contains an executable instruction which either JUMPs directly to the entry point of a compiled function or executes a CALL to an auxiliary routine that handles interpreted functions, undefined functions, or other special services (such as auto-loading functions, etc). The user can pass anonymous function objects around either as a code-pointer, which is a tagged object referring to a compiled code block, or a lambda expression, representing an interpreted function.

(putd Fname:id TYPE:ftype BODY:
lambda,code-pointer):id expr
Creates a function with name Fname and type TYPE, with body as the function definition. If successful, putd returns the name of the defined function.

If the body is a lambda there are two possible outcomes. When the switch comp is set to t the body will be compiled and a special instruction to jump to the start of the code is placed in the function cell. If the switch *comp is set to nil then the body is saved on the property list under the indicator *lambdalink and a call to an interpreter function

    (lambdalink) is placed in the function cell.

If the body is a code-pointer then a special instruction to jump to the start of the code is placed in the function cell.

The Type is recorded on the property list of Fname if it is not an expr.

After using putd on Fname, getd will return a pair which specifies the type and the body of the definition.

The following switches are useful when defining functions.

*redefmsg = [Initially: t] switch
   
If *redefmsg is not nil, the message

⋆⋆⋆ Function ‘FOO' has been redefined

is printed whenever a function is redefined.

*usermode = (Initially: t) switch
   
Controls action on redefinition of a function. All functions defined when *usermode is t are flagged USER. Functions which are flagged USER can be redefined freely. If an attempt is made to redefine a function which is not flagged USER, the query

Do you really want to redefine the system function FOO?

is made, requiring a Y, N, YES, NO, or B response. B starts the break loop, so that one can change the setting of *usermode. After exiting the break loop, one must answer Y, Yes, N, or No (See yesp in Chapter 12). If *usermode is nil, all functions can be redefined freely, and all functions defined have the USER flag removed. This provides some protection from redefining system functions.

*comp = [Initially: nil] switch
   
The value of *comp controls whether or not putd compiles the function before defining it. If *comp is nil the function is defined as a lambda expression. If *comp is non-nil, the function is first compiled. Compilation produces certain changes in the semantics of functions, see Chapter 19 for information.

(getd U:any): nil, pair expr
If U is not the name of a defined function, nil is returned. otherwise (expr, fexpr, macro, nexpr . code-pointer, lambda) is returned.

(copyd NEW:id OLD:id): NEW:id expr
Normally the function body and type of NEW become the same as those of OLD. However, if the switch *comp is set to t and the body of OLD is not compiled then NEW will be set to the compiled version of the body of OLD. If no definition exists for OLD an error:
    ⋆⋆⋆⋆⋆ OLD has no definition in COPYD

is given. NEW is returned.

(remd U:id): nil, pair expr
Removes the function named U from the set of defined functions. Returns the (ftype . function) pair or nil, as does getd. If the function type is not expr then it was recorded on the property list when the function was defined. In such cases this function removes the type information from the property list.

8.1.4 Function Definition in LISP Syntax

The functions de, df, dn, dm, and ds are used in PSL to define functions and macros. The functions are compiled if the compiler is loaded and the switch comp is set to t.

(de Fname:id PARAMS:id-list [FN:form]): id macro
Defines the function named Fname, of type expr. The forms FN are made into a lambda expression with the formal parameter list PARAMS, and this is used as the body of the function.

Previous definitions of the function are lost. The name of the defined function, Fname, is returned.

The COMMON module defines the macro DeFun which is equivalent to de.

(df Fname:id PARAM:id-list [FN:form]): id macro
Defines the function named Fname, of type fexpr. The forms FN are made into a lambda expression with the formal parameter list PARAM, and this is used as the body of the function. The parameter list should only contain one parameter.

Previous definitions of the function are lost. The name of the defined function, Fname, is returned.

(dn Fname:id PARAM:id-list [FN:form]): id macro
Defines the function named Fname, of type nexpr. The forms FN are made into a lambda expression with the formal parameter list PARAM, and this is used as the body of the function. The parameter list should only contain one parameter.

Previous definitions of the function are lost. The name of the defined function, Fname, is returned.

(dm Mname:id PARAM:id-list [FN:form]): id macro
Defines the function named Fname, of type macro. The forms FN are made into a lambda expression with the formal parameter list PARAM, and this is used as the body of the function. The parameter list should only contain one parameter.

Previous definitions of the function are lost. The name of the defined function, Fname, is returned.

The function list can be defined as follows

    (dm list (a)  
      (if (< (length a) 2) ()  
        (cons 'cons  
              (cons (second a)  
                    (ncons (cons 'list (rest (rest a))))))))

Now consider what occurs during the evaluation of (list 1 2). The list (list 1 2) is passed to the list macro which returns (cons 1 (list 2)) for further evaluation. The evaluator will call the list macro again for (list 2). This second call on the macro will return (cons 2 (list)). Finally, (list) is transformed into nil by a third call on the list macro and the entire process will terminate after evaluating (cons 1 (cons 2 nil)). Notice the lack of distinction between program and data The data structure representation of the function call is passed to the function as its parameter.

(ds Sname:id PARAMS:id-list [FN:form]): id macro
Defines the function named Sname of type macro. The syntax of ds is similar to that of de except that a macro is defined instead of an expr.

Perhaps the behavior of this function is best described with an example. The evaluation of

    (ds first (x) (car x))

will generate an expression similar to

    (dm first (a)  
      (prog (x)  
            (setq a (cdr a))  
            (setq x (car a))  
            (return (list 'car x))))

which is then evaluated. A sequence of assignment statements are created to initialize each parameter to its corresponding argument. Each id within the body FN which is not in the parameter list is quoted. It is an error to call a macro defined this way with more arguments than are specified by the parameter list. An error of this type will cause the message

    ⋆⋆⋆⋆⋆ Argument mismatch in SMacro expansion

to be printed. When a call is made without enough arguments the additional parameters are set to nil.

The following macro utilities are in the useful module.

(defmacro A:id B:form [C:form]): id macro
Defmacro is a useful tool for defining macros. The form of an application of defmacro is given below, square brackets are used to indicate zero or more occurances of an expression.
    (DEFMACRO <name> <pattern> [<form>])

The pattern is an list, each element is either an id or a pair. All of the ids in the pattern are local variables which may be used freely in the body (the ¡form¿s). When the macro is called the pattern is matched against the cdr of the macro call, binding the ids of the pattern to their corresponding parts of the call. Once the binding is complete the body is evaluated, the result is the value of the last form. Defmacro is often used with backquote. The following examples illustrate the use of defmacro. The first is intended to provide a contrast with ds.

    (defmacro car (s)  
      ‘(car ,s))

    (defmacro nth (s i)  
      (if (onep i)  
        ‘(car ,s)  
        ‘(nth (cdr ,s) ,(sub1 i))))

(deflambda name:id PARAMS:id-list [FN:form]): id macro
Defines the function named name, of type macro. Deflambda is similar to ds except that the body of the definition is enclosed within a lambda expression. The number of parameters of the lambda expression is the same as the number specified by PARAMS. When the macro is applied each of the arguments are evaluated and then bound to the parameters of the lambda expression. Finally, the body of the lambda is evaluated. Note that each argument is evaluated once. This may not be true had the macro been defined using ds. The following example illustrates when deflambda should be used in place of ds. The expansion of the first version of Check contains two occurances of (long-calculation n), where the expansion of the second version only contains one.
    (ds check (any)  
      (when (> any 0) (compute any)))  
 
    (deflambda check (any)  
      (when (> any 0) (compute any)))  
 
    (check (long-calculation n))  
 
    (when (> (long-calculation n) 0)    % Expansion of the first  
      (compute (long-calculation n)))   % version of check  
 
    ((lambda (any)                      % Expansion of the second  
       (when (> any 0)                  % version of check  
         (compute any)))  
     (long-calculation n))

8.1.5 BackQuote

(backquote A:form): form macro
Backquote is the function name for ‘ (accent grave). With some printers it may be difficult to distinguish between the quote and accent grave characters. For this reason, in the examples which follow ’expression is written as (quote expression). You must load USEFUL to define backquote.
In the previous section the function list was defined as a macro.
    (dm list (a)  
      (if (< (length a) 2)  
        ()  
        (cons (quote cons)  
              (cons (second a)  
                    (ncons (cons (quote list)(rest (rest a))))  
    ))))

The body of this definition is somewhat difficult to read. An abbreviated syntax was developed to aid both the writer and the reader. The notation, called ”backquote” (‘), works as an ”anti-quote”. Unquoted forms within its scope are assumed to be constants. To indicate that a form should be evaluated, one should prefix the form with a comma (,). Consider the redefinition of the macro list.

    (dm list (a)  
      (if (< (length a) 2)  
        ()  
        ‘(cons ,(second a)  
               ,(cons (quote list) (rest (rest a))))))

While this is an improvement, the explicit construction ,(cons (quote list) (rest (rest a))) clutters up the appearance. The application of cons is there to indicate that we want to combine the result of (rest (rest a)) with the identifier list. The form ,(list (rest (rest a)) will not give this effect, instead it would create a list of two elements. The at-sign (@) is used in conjunction with the comma to mean ”splice-in” rather than ”cons-in”. This results in the following defintion.

    (dm list (a)  
      (if (< (length a) 2)  
        ()  
        ‘(cons ,(second a) (list ,@(rest (rest a))))))

(unquote A:any): Undefined fexpr
Function name for comma ,. It is an error to eval this function; it should occur only inside a backquote.

(unquotel A:any): Undefined fexpr
Function name for comma-atsign ,@. It is an error to eval this function; it should only occur inside a backquote.
The two examples which follow are similar definitions of an arithmetic if form. There are four arguments: a test form, a negative form, a zero form, and a positive form. One of the last three forms is chosen to be evaluated according to whether the test form is positive, negative, or zero. The first uses the traditional dm while the second uses defmacro and backquote. Clearly the second is much simpler. To clarify printer output, there are no occurances of the accent grave character in the first definition and the second definition does not contain an occurance of the quote character.
(dm number-if (calling-form)  
  (list 'let  
        '((var (gensym)))  
        (list 'let  
              (list (list 'var (nth calling-form 2)))  
              (list 'cond  
                    (list (list 'minusp 'var)  
                          (nth calling-form 3))  
                    (list (list 'zerop 'var)  
                          (nth calling-form 4))  
                    (list t (nth calling-form 5))))))  
 
(defmacro number-if (test minus-form zero-form plus-form)  
  (let ((var (gensym)))  
    ‘(let ((,var ,test))  
       (cond ((minusp ,var) ,minus-form)  
             ((zerop ,var) ,zero-form)  
             (t ,plus-form)))))

8.1.6 MacroExpand

(macroexpand A:form [B:id]): form macro
Macroexpand is a useful tool for debugging macro definitions. If given one argument, macroexpand expands all the macros in that form. Often one wishes for more control over this process. For example, if a macro expands into a let, we may not wish to see the let itself expanded to a lambda expression. Therefore additional arguments may be given to macroexpand. If these are supplied, they should be the names of macros, and only those specified are expanded.

Low Level Function Definition Primitives

The following functions are used especially by putd and Getd, defined above in Section 9.1.3, and by eval and apply, defined in Chapter 11.

(funboundp U:id): boolean expr
Tests whether there is a definition in the function cell of U; returns nil if so, t if not.

Note: The functional value of an identifier which does not define a function is actually a call on undefinedfunction. The evaluation of undefinedfunction will result in a continuable error.

(fboundp U:id): boolean macro
Equivalent to (not (funboundp U)), the function funboundp is described above.

(flambdalinkp U:id): boolean expr
Tests whether U is an interpreted function; return t if so, nil if not. This is done by checking for the special code-address of the lambdalink function, which calls the interpreter.

(fcodep U:id): boolean expr
Tests whether U is a compiled function; returns t if so, nil if not.

(makefunbound U:id): nil expr
Makes U an undefined function by planting a special call to the function, undefinedfunction, in the function cell of U. See funboundp above for more information about undefinedfunction.

(makeflambdalink U:id): nil expr
Makes U an interpreted function by planting a special call to an interpreter support function (lambdalink) function in the function cell of U.

(makefcode U:id C:code-pointer): nil expr
Makes U a compiled function by planting a special JUMP to the code-address associated with C.

(getfcodepointer U:id): code-pointer expr
Gets the code-pointer for U.

(code-number-of-arguments C:code-pointer): {nil,integer} expr
Some compiled functions have the argument number they expect stored in association with the code-pointer C. This integer, or nil is returned.

8.1.7 Function Type Predicates

(exprp U:any): boolean expr
Test if U is a code-pointer, lambda form, or an id with expr definition.

(fexprp U:any): boolean expr
Test if U is an id with fexpr definition.

(nexprp U:any): boolean expr
Test if U is an id with nexpr definition.

(macrop U:any): boolean expr
Test if U is an id with macro definition.