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.