define is a Pop-11 syntax word used to define procedures and their updaters. A procedure definition has the form:
define <header-line>; <body> enddefine;
The header-line describes the type of the procedure, its arguments and results, and possibly the pdnargs and pdprops. The body is any Pop-11 expression sequence, including declarations of local variables, nested procedures and dynamic local expressions. It is also possible to create anonymous procedures (see HELP * PROCEDURE).
The definition of the * updater of a procedure has the form:
define updaterof <header-line>; <body> enddefine;
The full specification of procedure definitions can be found in REF * POPSYNTAX/define. Only the main cases are specified here. They should suggest the generalisations to remaining forms. TEACH * DEFINE gives an introduction to the basic facilities, for beginners.
ect headings to return to index
A procedure definition does two things; it declares an identifier (the name of the procedure) and assigns it a value (a procedure record).
The procedure record will have associated with it a pdprops field, normally the name of the procedure, and a pdnargs field, which is normally the number of input parameters specified in the procedure definition. Thus:
define proc(a, b, c) -> d -> e; <expression sequence> enddfine;
defines a procedure of three arguments (pdnargs = 3), and two results with the name "proc", assigned to the pdprops of the procedure. The name "proc" is defined as an ordinary global variable and the procedure is assigned to be its valof.
Five local variables, three input locals and two output locals, will be declared automatically, and will default to being declared lexical, unless they are declared dynamic within the procedure using dlocal.
The first two lexical variables named will normally be allocated to registers. See HELP * LVARS.
If the procedure has any result variables these should be specified thus:
define foo() -> x; ;;; for one result define foo() -> x -> y; ;;; for two results define foo() -> x -> y -> z; ;;; for three results (and so on).
When the procedure execution terminates the values of the result variables are stacked in reverse order. The result variables, like arguments, are made local variables of the procedure. They are therefore often referred to as output locals.
It is not necessary for a procedure to have result variables for it to return a result. If preferred, the results may be stacked explicitly by the procedure, as in:
define sum_of_squares(x, y); x * x + y * y enddefine;
TEACH * STACK explains what happens when such a procedure runs.
A procedure may have an updater associated with it, which is invoked when the name occurs on the right of an assignment. E.g. we define a variable holding a reference and a procedure to access or update the reference.
vars storevar = consref(0);
define store(); cont(storevar) enddefine;
In this case the result is left on the stack:
store() => ** 0
To change the value stored we can define an updater:
define updaterof store(x); x -> cont(storevar) enddefine;
99 -> store(); store() => ** 99
See HELP * UPDATER and HELP * UPDATEROF.
The above forms define "proc" and "store" as ordinary identifiers, whose values just happen to be a procedure. This is the default type if popdefineprocedure and popdefineconstant are both false.
The procedure header-line can specify the type of the identifier naming the procedure. There are several main types of procedure identifier: ordinary, operation, macro, syntax, and active. In addition the identifier may be lexical or non-lexical, variable or constant, global or not. Some of these are illustrated below.
For more information on syntactic types of identifiers see HELP * IDENTPROPS and HELP * IDENTTYPE
The main difference between ordinary identifiers and identifiers of type operation, macro, syntax or active concern how they are invoked.
A normal procedure identifier is simply an identifier that has a procedure as its value. Its identprops is 0, like any other identifier,
identprops("hd") => ** 0
and unlike an infix operator, whose identprops is a non-zero number:
identprops("+") => ** 5
In order to indicate that an ordinary procedure is to be run the identifier is either preceded by "." or followed by a pair of parentheses, possibly containing argument expressions separated by commas. E.g.
.store => ** 99
This is equivalent to doing:
store() => ** 99
Similarly system procedures can be invoked using either the dot:
16.sqrt => ** 4.0
or parentheses:
sqrt(16) => ** 4.0
If the procedure name is used without the extra syntax (dot before, or parentheses after), then its value is simply left on the stack, as with any other normal identifier, unless it occurs on the right of an assignment, in which case it may receive a new value. E.g if "foo" is an ordinary procedure identifier:
foo(a,b,c) - puts a,b,c on the stack and runs FOO pr(foo) - puts the procedure on the stack and runs PR 99 -> foo - gives FOO a new value, the number 99.
foo(x + 1, y) - puts x, then 1 on the stack, calls "+", then puts y on the stack then calls foo.
(See TEACH * STACK.)
Some procedures have names that are operation identifiers. They have an identprops value that is an integer /== 0.
They can be invoked by writing the procedure name between its arguments. Often, but not always infix operations procedures have two arguments. For example '+' is an infix procedure, thus:
3 + 4 => ** 7
as are '<>' and '>':
[a b c] <> [d e f] => ;;; join two lists ** [a b c d e f]
4 > 5 => ** <false>
Infix procedures have a numerical precedence (between -12.7 and +12.7) to disambiguate expressions such as:
3 + 4 * 5
In such expressions, infix procedures whose precedences have the lowest absolute value are evaluated first. The precedence of '*' is 4 and that of '+' is 5 so the above expression is equivalent to '3 + (4 * 5)'. Otherwise infix procedures are evaluated left to right if the precedence is positive, right to left if negative.
define can be followed by an integer or decimal number between -12.7 and +12.7 in order to specify that the procedure name is an operation identifier with that precedence. For example,
define -3 sumsq(a, b) -> c; a*a + b*b -> c enddefine;
or, since there are exactly two arguments we can omit the parentheses on header line and put the procedure name between the argument names.
define -3 a sumsq b -> c; a*a + b*b -> c enddefine;
declares "sumsq" as an operation identifier with precedence -3.
3 sumsq 4 => ** 25
Operations with negative precedence associate to the right, with positive precedence to the left. So sumsq associates to the left.
2 sumsq 3 sumsq 4 => ** 629
(2 sumsq 3) sumsq 4 => ** 185
2 sumsq (3 sumsq 4) => ** 629
Operation identifiers are used to invoke procedures without the use of "." or parentheses. Examples of built in operation identifiers are:
+ = > :: <> -
See HELP * SYSWORDS for more.
Operation identifiers may be invoked with any number of arguments, in any of the following forms:
op ;;; no arguments op a1 ;;; one argument a1 op ;;; one argument a1 op a2 ;;; two arguments a1,a2, ... ,aN-1 op aN ;;; N arguments
They may also be invoked with arguments between "(....)", like ordinary procedure identifiers:
sumsq(3,4) => ** 25
If an operation identifier is used on the right of an assignment, its updater will be invoked. If there is no updater, an error occurs:
99 -> 3 sumsq 4; ;;; MISHAP - EXECUTING NON-EXISTENT UPDATER ;;; INVOLVING: <procedure sumsq>
Normal occurrences of an operation identifier will cause it to be invoked, and if it requires arguments an error may result.
sumsq => ;;; MISHAP - STE: STACK EMPTY (missing argument? missing result?) ;;; DOING : mishap sumsq compile
Invocation can be suppressed by using nonop:
nonop sumsq => ** <procedure sumsq>
Using nonop a value may be assigned to the identifier. E.g.
conspair -> nonop sumsq;
3 sumsq 4 => ** [3|4]
If an operation identifier is preceded by nonop it then behaves like an ordinary identifier. See HELP * OPERATION and HELP * NONOP
Only a procedure may be assigned to an operation identifier so there need be no run-time checking that sumsq has a procedure value, as there is with ordinary procedure names.
The recommended syntax for the header-line of an infix operation with two arguments and no result variables is:
define <precedence> <argument1> <name> <argument2>;
For example:
define 4 x foo y;
This defines foo to be an infix operation of precedence 4 with two arguments x and y and no result variables.
Result variables are specified as for normal procedures, for example:
define 4 x foo y -> z;
It is possible to define infix operations with one or no arguments (strictly, these should be called unary and nonary procedures). For example:
define 4 foo x; ;;; one argument define 4 foo; ;;; no arguments
Be careful with nonary procedures. A call of a nonary procedure looks just like a variable access.
A procedure identifier may be declared to be of type macro, as in:
define macro cube x; x, "*", x , "*", x enddefine;
When the word "cube" is read by * itemread (used by the Pop-11 compiler), whether inside a procedure definition or at top level the procedure is run immediately. It reads one more item (e.g. a word or number) from the input text stream assigns it to X, then creates a new expression of the form 'X * X * X', which is then read by the compiler. So typing 'cube 55' anywhere is exactly equivalent to typing '55 * 55 * 55'. This may be more efficient than defining a procedure cube, as the multiplication instructions are "planted inline" wherever "cube" is used, avoiding the overhead of invoking an extra procedure to do the multiplication.
Macros can also do things that cannot be done using procedures. For an example see the macro swap defined in HELP * MACROS.
Macro procedures are evaluated at compile time (ie when read in by the compiler). Their arguments (if they have any) are bound to the follwing text items, not to expressions (as Lisp users might expect). So because cube has one argument, the expressions:
cube 7 or cube x
will work, but not:
cube hd(x) or cube (a + b)
Since in this case cube would get "(" as the value of X, producing the nonsensical program text:
( * ( * ( a + b
When a macro identifier is read by itemread the procedure it names is run immediately. The procedure can read in and re-arrange the compiler input stream * proglist. This may be useful for defining syntactic extensions to the language, so that complex constructs may be expressed simply. For a complex library macro extending the syntax of Pop-11 in a useful way, see LIB * SWITCHON, documented in HELP * SWITCHON
Because they are run when read by itemread macros themselves are never seen by the compiler. The effect can be disabled if they are preceded by nonmac. See HELP * MACRO & HELP * NONMAC.
nonmac cube => ** <procedure cube>
The recommended syntax for the header-line of a macro procedure is:
define macro <name> <argument1> <argument2> ...;
Notice that the arguments are not separated by commas or put in parentheses. Result variables (if any) are specified as for normal procedures though it is unusual for macros to have result variables. Usually macros have many results, that is they leave many things on the stack. These are spliced into the input stream to the compiler in place of the macro call, i.e. they are added to the front of proglist.
REF * PROGLIST explains the role of proglist and macros etc. during compilation.
The recommended form for the header-line of a syntax procedure is:
define syntax <name>;
Syntax procedures should have neither arguments nor results. They achieve their effects by calling compiler routines to plant machine language instructions.
Like macros, syntax procedures are executed at compile time. System syntax words, such as if, define, & until correspond to syntax procedures.
define syntax foo; <action> enddefine;
define syntax 5 foo; <action> enddefine;
Each of these defines "foo" as a syntax word, the latter as a syntax operator of precedence 5.
When syntax words have a precedence this is used in parsing the input stream during compilation. E.g "(" is a syntax word of precdence -1, "->" a syntax word of precedence 11. See HELP * IDENTPROPS
When a syntax identifier is read by the compiler, the procedure it names is run immediately. Normally this will call code-planting procedures of the kinds defined in REF * VMCODE to compile an expression, or expression sequence, or perform declarations, etc. itemread does not run syntax procedures itself. Closing brackets can be declared as syntax identifiers, in order that they may trigger syntax checking.
For an example of the definition of a new syntax word for looping over items in the Pop-11 database, see LIB * FOREACH, documented in HELP * FOREACH.
See HELP * SYSWORDS for some built in syntax words.
Active identifiers are associated with procedures which are run when the identifiers are accessed or updated. Each has a multiplicity, an integer N specifying how many values the active variable can store. N defaults to 1 if not specified. The activation of the procedure is suppressed if the identifier is preceded by nonactive.
See HELP * ACTIVE_VARIABLES for details.
The main procedure heading can end (after output locals and before the semi colon) with either or both of with_props or with_nargs specifications.
define ....... with_props foo;
indicates that the word "foo" should be assigned to the pdprops of the procedure rather than the normal name. See HELP * WITH_PROPS
define ...... with_nargs <integer>;
Indicates that <integer> should be assigned to the pdnargs of the procedure rather than the default number suggested by the number of formal input parameters specified in the definition. See HELP * WITH_NARGS.
Procedure definitions may be nested within other procedure definitions or within anonymous procedures defined by procedure ... endprocedure. In either case the name of the procedure will be a local identifier. It may be lexical or non-lexical, depending on the type specifications used. (It defaults to lconstant).
It is often convenient to redefine the interrupt procedure * interrupt, or the procedure that prints error messages, * prmishap, as local procedures, so that in certain contexts they will not have their normal effects. An example of a procedure that re-defines interrupt can be found in LIB * POPREADY, documented in HELP * POPREADY
Note that the default scope of the name of nested procedure definition is lconstant. If you want to locally redefine a global procedure variable, like interrupt or prmishap, you must include the word dlocal in the procedure header, like this:
define dlocal interrupt ...
The name may be declared as lexical or non-lexical, the default for a top level procedure being non-lexical (dynamic), and the default for a nested procedure being lexical. See HELP * LEXICAL and REF * IDENT. To indicate that a lexical identifier is required, follow define with one of:
lvars - a lexical variable: may be re-assigned lconstant - a lexical constant: may not be re-assigned
E.g.
define lvars proc(a, b) -> c; define lconstant proc(a, b);
If procedure names defined in a file are declared as lexical, then they cannot be accessed except by programs in the same file. This is useful for preventing unintended interactions without using * sections.
If it is required that the procedure should be acessible in all sections below the one in which it is being defined (or to which it is exported) then the name should be declared as a global identifier. (This is meaningless if it is made lexical.)
E.g. define global foo(a, b)
define global vars foo(a, b)
global vars foo;
followed later by:
define foo ....
If foo is an ordinary identifier whose value is a procedure, then an instruction to run foo (for instance in another procedure definition) compiles into machine code operations that include a check to ensure that the value really is a procedure, in case something else has been assigned to the identifier. This will detect errors like this.
define foo(list); hd(tl(list)) enddefine;
define test(list); foo(list) enddefine;
test([a b c]) => ** b
999 -> foo;
test([a b c])=> ;;; MISHAP - ENP: EXECUTING NON-PROCEDURE ;;; INVOLVING: 999 ;;; DOING : mishap C test compile
This run-time procedure check can slow programs down. In order to avoid it, users can declare certain identifiers to be of type procedure, so that the check is done only when a value is assigned to the identifier, not when the procdure is run. There are two formats for such a declaration:
vars procedure foo;
or
define procedure foo(list); hd(tl(list)) enddefine;
define test(list); foo(list) enddefine;
test([a b c]) => ** b
So far as before. But now:
999 -> foo; ;;; MISHAP - ASSIGNING NON-PROCEDURE TO PROCEDURE IDENTIFIER ;;; INVOLVING: 999 foo ;;; DOING : mishap compile
For more on the use of procedure identifiers, see HELP * EFFICIENCY. Constants and lexical identifiers may also be declared to be of type procedure, as in:
define constant procedure foo(...) define lvars procedure foo(...) define lconstant procedure foo(...)
Infix operations, macro names, syntax words, and active identifiers, are automatically of type procedure. If the variable popdefineprocedure is not false, then all procedure names are automatically declared to be of type procedure.
Once an identifier has been declared to be of type procedure it cannot be re-declared not to be, e.g. using vars. This is because non-checking invocations of its value may already have been compiled.
A procedure identifier may be declared to be a constant. This will save an indirection in the procedure call, with a slight speed advantage. However, it will not then be possible to trace the procedure.
If a procedure identifier is made a constant, then if it is redefined all procedure definitions in which it is invoked will have to be re-compiled in order to access the new procedure.
Format: constant foo; lconstant foo; define constant foo; define constant procedure foo; define lconstant foo;
If popdefineconstant is not false, then all procedure definitions are taken to declare constant identifiers.
As already indicated, input and output parameters default to lexical local variables. The default can be over-ridden by declaring them locally as dynamic using dlocal.
Note that variables like cucharout, prmishap, popprompt, which may have to be accessible by other procedures, must be declared dlocal. Generally local loop counters and other temporary stores should be lexical locals.
The full semantics of lexical identifiers is explained in detail in REF * VMCODE in the section 'Implementation of Lexical Scoping'.
valof(w)
can always be used to access or update the value of "popprompt", a non-lexical Pop-11 identifier.
A procedure can specify that one or more of its input or output parameters is to be a variable of type procedure, thus:
define applyto (list, procedure proc); for x in list do proc(x) endfor enddefine;
For reasons of compatibility with earlier Pop-11 or Pop-2 systems, there are several formats for defining procedures. Some of the older formats may eventually be phased out. The recommended form for the header-line of a normal procedure with no results is:
define foo(<argument-list>);
The argument-list is a sequence of variable names separated by commas, for example:
define foo(x, y, z);
The arguments are automatically declared as local variables of the procedure.
Pop-11 has several archaic forms of header-line maintained for compatibility with older dialects of POP (such as Pop-2, Pop-10, WPOP).
It is not necessary to put any commas or parentheses in the header-line of the archaic form of a normal procedure. That is:
define foo x y;
is equivalent to:
define foo(x, y);
Similarly,
define foo x y => z;
has two arguments and produces one result.
This makes available some syntactic forms and procedures that were previously available in Pop-2.
In particular, after the library has been loaded, the word function may be substitued for define and the word end may be substituted for enddefine. See HELP * POP2.
The symbol '=>' may be used in place of '->' in the specification of result variables. Also, if there are several result variables and they are separated by commas or spaces then they are stacked in the order given. That is:
define foo() -> x -> y;
is equivalent to:
define foo() => y, x; or define foo => y x;
See also:
HELP * OPERATION, * MACRO, * SYNTAX, - types of procedure identifiers
HELP * POPDEFINECONSTANT - automatic declaration of procedure names as constants
HELP * POPDEFINEPROCEDURE - automatic declaration of procedure names to be type procedure
HELP * UPDATER, * UPDATEROF - on the form and use of procedure updaters
HELP * PROCEDURE - for use of Pop-11 syntax words procedure ... endprocedure
HELP * PDPROPS - information associated with the procedure
HELP * PDNARGS - the number of arguments required
HELP * DLOCAL - local variables and dynamically local expressions
HELP * VARS, * LVARS, * DLVARS, * LEXICAL - for use and declaration of variables
HELP * CONSTANT - use of constant identifiers
REF * POPCOMPILE/pop11_define_declare - for the use of popdefineprocedure and popdefineconstant.
REF * PROCEDURE - for information about the form of procedure records and procedures which operate on procedures.
--- C.all/help/define --- Copyright University of Sussex 1995. All rights reserved.