Search Top Index
HELP DEFINE A.Sloman November 1987 Revised Aaron Sloman Sept 1997 define .... enddefine On procedures and procedure definitions in Pop-11. See also TEACH * PRIMER/'CHAPTER.4' CONTENTS - (Use <ENTER> g to access sections) -- Introduction -- The role of a procedure definition -- Specifying output variables -- -- Use ENTER getvars to insert lvars declarations -- -- Obsolete syntax for output locals -- Procedures as arguments and results -- "define updaterof" -- The "dot" notation. -- -- Using the dot notation with more than one argument -- Referring to a procedure as an object -- The concept of a procedure in Pop11 -- Other types of procedure identifiers -- -- Operation identifiers -- -- Active variables -- -- Macros and syntax words -- -- Scoping of procedure identifiers -- Invoking different sorts of procedures -- Invoking operations -- Infix operators have a numeric "precedence" -- Defining operation identifiers -- Invoking infix operators -- Invoking the updater of an operation -- Using NONOP to suppress invocation -- Formats for defining operation identifiers -- Macros -- -- Warning to Lisp experts -- Syntax Procedures -- Active variables -- WITH_PROPS and WITH_NARGS -- Nested procedure definitions -- Making the procedure name a lexical identifier -- Making the procedure name global to sections -- Making a procedure name a procedure identifier -- Making a procedure name a constant -- -- compile_mode -- Declaring local variables -- Restrictions on the use of lexical locals -- Extending the define ... enddefine syntax, using define_form -- Alternative formats for procedure definitions -- Archaic forms -- LIB POP2 -- Archaic form for output locals -- RELATED DOCUMENTATION -- Introduction ------------------------------------------------------- The define .... enddefine syntax form in Pop-11 is used to define named procedures and their updaters. It can be used in several different formats. The editor VED knows about "define" and "enddefine" opening and closing brackets so it can help the user quickly locate a complete procedure definition, in order to mark it, or compile it, or globally replace some string within it, etc. (See * VEDOPENERS, * VEDCLOSERS) A standard 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 (a field in the procedure record indicating the number of arguments) and PDPROPS (a field indicating the name and possibly other properties of the procedure).. The body is any POP-11 expression sequence, including declarations of local variables, nested procedures and dynamic local expressions (using dlocal). The definition of the *UPDATER of a named procedure has the form: define updaterof <header-line>; <body> enddefine; As described in HELP * PROCEDURE, it is also possible to create anonymous procedures using the syntax: procedure ... endprocedure TEACH * PRIMER/'CHAPTER.4' gives more detailed information about procedures and how to define them. More information about procedure definitions can be found in the file REF *POPSYNTAX/define. Only the main cases are specified here. TEACH * DEFINE gives an introduction to the basic facilities, for beginners. Pointers to documentation on additional options are given below. -- The role of a procedure definition --------------------------------- A procedure definition does two things; it declares an identifier (the name of the procedure) and assigns it a value an actual Pop-11 procedure created from the definition. The value is a procedure record, usually containing compiled machine code derived from the procedure definition. 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; or, equivalently (not the reversal after "->"): define proc(a, b, c) -> e -> d; <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 value, i.e. it is assigned to valof("proc"). In the above example, five local variables (three input locals, a, b, c and two output locals d, e) will be declared automatically. They will be made local variables of the procedure called proc. Whether they are made lexical variables (lvars) or not, will depend on whether they are explicitly declared in the body of the procedure. Prior to Poplog Version 15 they default to being declared non-lexical, if not explicitly declared. From Poplog Version 15, the default is reversed: they are lexical unless declared to be "dynamic" variables, using vars and dlocal. (Dynamic variables are needed for the Pop-11 pattern matcher described in HELP * MATCHES, and for other purposes.) Normally it is preferable to make the declarations of variables explicit: define proc(a, b, c) ->(d, e); lvars a, b, c, d, e,; <expression sequence> enddfine; Depending on the machine used, the first few lexical variables named in the local LVARS declaration will be allocated to registers for extra speed of access. How many are allocated to registers depends on the machine architecture. See HELP * LVARS/registers -- Specifying output variables ---------------------------------------- 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 all of the result variables are left on the stack, in the order in which they were given in the procedure header. (See TEACH * STACK, or TEACH * PRIMER/'CHAPTER.3' NOTE: 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); lvars x, y; x * x + y * y enddefine; However, including output locals in the procedure heading makes clearer what the procedure is intended to do, and makes it easier for a new programmer to look after old programs. (There is a slight loss of efficiency, compared with simply stacking the results directly.) The output variables, like the input variables, are made local variables of the procedure. They are therefore often referred to as 'output locals', as contrasted with 'input locals'. -- -- Use ENTER getvars to insert lvars declarations This paragraph is no longer required for Poplog Version 15 or later, since input and output variables are now automatically declared as "lvars". The VED command ENTER getvars available at Birmingham, inserts lvars declarations automatically for all input variables and output variables, immediately after the procedure header. So you can use it after completing or modifying the header line. (If there was previously a vars line there, it is left unchanged after the new line, as it may have declared additional local variables. You may have to edit it.) -- -- Obsolete syntax for output locals Early versions of Pop-11 required the above to be expressed thus: define foo() -> x; ;;; for one result define foo() -> y -> x; ;;; for two results define foo() -> z -> y -> x; ;;; for three results (and so on). If this format is used, on exit the procedure will stack the values of output locals in the reverse order to that given in the header. -- Procedures as arguments and results -------------------------------- 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); lvars list, proc; lvars x; for x in list do proc(x) endfor enddefine; would define a procedure equivalent to the system procedure *APPLIST. E.g. applyto([a b c], npr); a b c If you try to give it a non-procedure as second argument a mishap results. applyto([a b c], 55); ;;; MISHAP - ASSIGNING NON-PROCEDURE TO PROCEDURE IDENTIFIER ;;; INVOLVING: 55 proc ;;; DOING : mishap applyto ... Alternatively the specification that the second argument must be a procedure can occur in the lvars declaration. define applyto (list, proc); lvars list, procedure proc; lvars x; for x in list do proc(x) endfor enddefine; Note that one procedure can define another inside it, if it is to be used nowhere else. Normally it is best defined as lconstant. For example here is a procedure that takes a list of numbers and returns a list of their squares define squares(list) -> result; lvars list, result; define lconstant square(num); lvars num; num*num enddefine; ;;; now make a list of squares using applyto [% applyto(list, square) %] -> result enddefine; Test it: squares([ 1 2 3 4]) => ** [1 4 9 16] Note that nested procedure definitions can also contain nested definitions. There is no limit to the depth of nesting. Sometimes a procedure is nested because it is used to refer to a variable that is local to the enclosing procedure. There are further comments on nested procedure definitions below, and (for experts) in REF * VMCODE, which explains why making the procedure lconstant improves efficiency. Many forms of sophisticated "higher order" programming depend on the fact that procedures can be returned as results. If the procedure that is returned as a result includes a variable that is not local to it but is local to the enclosing procedure, declared using lvars, then when the result is returned it is an "instance" of the original nested procedure, with the non-local variable replaced by its value at execution time. Examples are given in HELP * LVARS in the section on lexical closures. (This is for sophisticated users only.) -- "define updaterof" ------------------------------------------------- A procedure may have an updater associated with it, which is invoked when the name occurs on the right of an assignment, in the format ... -> foo(...); E.g. we can define a variable called storevar holding a Pop-11 "ref" record, and a procedure to access or update the contents of the record, using the procedure cont and its update. See *consref, *cont. vars storevar = consref(0); define store() -> val; lvars result; cont(storevar) -> val; enddefine; When the procedure is invoked the result is left on the stack. store() => ** 0 To change the value stored we can define an updater of store, thus: define updaterof store(x); lvars x; x -> cont(storevar) enddefine; 99 -> store(); store() => ** 99 [the cat] -> store(); store() => ** [the cat] See HELP *UPDATER, *UPDATEROF Many system procedures come with their updaters already defined, so that they are invoked on the right of an assignment. As the above example indicates cont is one of them. Others are: hd, tl, front, back, subscrs, subscrv, valof -- The "dot" notation. ------------------------------------------------ Normally a procedure is invoked by following its name with parentheses, enclosing arguments if there are any, e.g store() => ** [the cat] sqrt(4.0) => ** 2.0 Instead of parentheses after the procedure name, a dot "." can be used just before the procedure name, e.g. .store => ** [the cat] Similarly procedures that require an input can be invoked using either parentheses sqrt(16.0) => ** 4.0 or the dot notation: 16.0.sqrt => ** 4.0 Notice that in the above the first "." is part of the number specification for the decimal number 16.0, and the second "." indicates that a procedure is being applied. A space can occur before the second dot, but not before the first. 16.0 .sqrt => ** 4.0 whereas this version attempts to treat 0 as a procedure, after the first dot: 16. 0.sqrt => and produces an obscure error message: ;;; MISHAP - COMPILING CALL TO NON-STRUCTURE ;;; INVOLVING: 0 -- -- Using the dot notation with more than one argument If a procedure takes two or more arguments, e.g. substring, then it can be invoked with the arguments between parentheses separated by commas, e.g. substring(4, 3, 'In the house') => ** the or using the dot notation by separating the arguments with commas, without parentheses: 4, 3, 'In the house'.substring => ** the Sometimes repeated uses of the dot notation can be clearer than nested procedure calls. E.g. these two are equivalent, but some people find the second easier to read: hd(tl(tl([cat dog mouse pig]))) => ** mouse [cat dog mouse pig].tl.tl.hd => ** mouse Note that when using the dot notation the order of occurrence of procedure names is reversed: the last procedure call in the dot notation corresponds to the "outermost" (i.e. first) procedure in the first notation. -- Referring to a procedure as an object ------------------------------ If the procedure name is not an infix operator (like "+") then it can be used as an ordinary variable to refer to the procedure. I.e. it is used without the extra procedure-invocation syntax (dot before, or parentheses after). 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 [the procedure ^foo] - makes a list with foo as its third element 99 -> foo - gives FOO a new value, the number 99. -- The concept of a procedure in Pop11 -------------------------------- Pop-11 procedures a recognizer procedure isprocedure which can be applied to any object and will return true if it is a procedure otherwise false. isprocedure(sqrt) => ** <true> but its name is not one isprocedure("sqrt") => ** <false> isprocedure([a list]) => ** <false> Notice that it recognizes itself: isprocedure(isprocedure) => ** <true> (If you know about Russell's paradox you can think about that example.) In Pop-11 besides ordinary procedures defined using the syntax described above and the other forms described below there are types of objects that contain data that are treated as if they were procedures. Examples are o properties See HELP * PROPERTIES for a list of relevant documentation o arrays See HELP * ARRAYS We can illustrate by creating a property prop and an array arr (don't worry if you don't know what they are) vars prop = newproperty([], 10, false, false); vars arr = newarray([1 2 3 4]); Both of these are recogized as procedures: isprocedure(prop) => ** <true> isprocedure(arr) => ** <true> But prop is also recognized as a property isproperty(prop) => ** <true> and similarly isarray(arr) => ** <array [1 2 3 4]> (isrray returns the array itself, rather than true, for reasons that have to do with how it recognizes more complex cases, as explained in REF * isarray) -- Other types of procedure identifiers ------------------------------- We previously showed how to define two procedures, proc and store. The above forms define "proc" and "store" as ordinary identifiers, whose values just happen to be a procedure. There are several main types of procedure identifier, ordinary, operation, macro, syntax, and active. The main difference between ordinary identifiers and identifiers of these types is concerned with how they are invoked and what happens when they are invoked. The procedure header-line can specify the type of the identifier naming the procedure. -- -- Operation identifiers An operation identifier (like many of the arithmetic operators) can be inserted between its arguments, without any parentheses, as in 3 + 5 99 / (6 * 5) -- -- Active variables An active variable is a bit like an operator which takes no arguments, yet returns a result (or more than one). It can also, unlike an operator, be used with "dlocal" to make its value local to a procedure. See HELP * ACTIVE_VARIABLES for more on active variables.) -- -- Macros and syntax words Macro and syntax identifiers are used to extend the syntax of Pop-11, e.g. introducing new forms of loops or other expressions. The syntax they use is not fixed, since they can change the syntax almost arbitrarily. Unlike the other sorts of procedures mentione they are invoked at compile time, when the Pop-11 compiler is reading in Pop-11 text. When it finds a macro or syntax word it allows the associated procedure to take over reading in and compiling text, up to the occurrence of some closing bracket. For example "foreach" is a syntax word which reads in text up to "endforeach" and then compiles instructions for a special kind of loop. All the main Pop-11 syntax forms are implemented using syntax words which take over compilation when they are encountered. A macro does not do any actual compiling. Instead it reads in some Pop-11 text and then replaces it with some different text, after which the normal compiler takes control again. For more information on syntactic types of identifiers see HELP * IDENTPROPS, * IDENTTYPE -- -- Scoping of procedure identifiers Besides having a syntactic role and having a procedure as value, a procedure identifier may either be lexically scoped or dynamically scoped (e.g. declared with lvars or vars). It may also be constant or variable (re-definable), global to sections, or not. It may also be local to another procedure, if defined within it. Some of these options are illustrated below. -- Invoking different sorts of procedures ----------------------------- 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 invoked the identifier is followed by a pair of parentheses, possibly containing argument expressions separated by commas. E.g. store() => ** 99 However the syntax used is different for infix operators. -- Invoking operations ------------------------------------------------ Some procedures have names that are operation identifiers. Such an identifier (e.g. +, > =) names a procedure that can be invoked without using parentheses or a dot. 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> However, "-" can be used as both unary operator that takes one input as in - 66 => ** -66 or as a binary operator that takes two inputs (one each side): 77 - 66 => ** 11 -- Infix operators have a numeric "precedence" ------------------------ The procedure identprops can be applied to the name of another procedure to find out what type of use it has. An ordinary procedure name has an identrops value of 0. identprops("hd") => ** 0 identprops("substring") => ** 0 whereas infix operators have an identprops value that is a number value other than 0: identprops("+") => ** 5 identprops(">") => ** 6 identprops("=") => ** 7 Infix operators have a numerical precedence (between -12.7 and +12.7). The numeric value is used 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. (WARNING: In many other languages the operators with HIGHEST precedence are evaluated first. This difference sometimes causes confusion.) -- Defining operation identifiers ------------------------------------- Users can define their own infix operators. "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; lvars a, b, c; a*a + b*b -> c enddefine; Check that it has a non-zero precedence. identprops("sumsq") => ** -3 Since there are exactly two arguments Pop-11 allows us to omit the parentheses on the header line and put the procedure name between the argument names. define -3 a sumsq b -> c; lvars a 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 right. 2 sumsq 3 sumsq 4 => ** 629 (2 sumsq 3) sumsq 4 => ** 185 2 sumsq (3 sumsq 4) => ** 629 Examples of built in operation identifiers in Pop-11 are: You can apply identprops to each of these to find out its precedence: "+", "=", ">", "::", "<>", "-" lvars word; for word in [+ = > :: <> -] do pr(word); pr(tab); pr(identprops(word)); pr(newline) endfor; + 5 = 7 > 6 :: 4 <> 5 - 5 See HELP * SYSWORDS for more. -- Invoking infix operators ------------------------------------------- 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 -- Invoking the updater of an operation ------------------------------- 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> -- Using NONOP to suppress invocation --------------------------------- 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> If we wish to give the value of an infix procedure to another procedure, e.g. a recognizer, we can use nonop, thus: isprocedure( nonop sumsq ) => ** <true> isprocedure( nonop = ) => ** <true> isword( nonop sumsq ) => ** <false> That the value of an operation identifier is just an ordinary procedure. The difference between a normal type of procedure identifier and an infix operator is only concered with the syntax used to define it and the syntax used to invoke it. The actual object associated with the identifier is just an ordinary Pop-11 procedure. Using NONOP, a new value may be assigned to an infix operator 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, *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. e.g. trying to assign a non-procedure produces a mishap: [the cat] -> nonop sumsq; ;;; MISHAP - ASSIGNING NON-PROCEDURE TO PROCEDURE IDENTIFIER ;;; INVOLVING: [the cat] sumsq -- Formats for defining operation identifiers ------------------------- 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. -- Macros ------------------------------------------------------------- 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" defined in that way is read 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 e.g. vars xx = cube 55; xx => ** 166375 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. A macro can have more than one argument. It will read in as many items from the program input stream as it has arguments. -- -- Warning to Lisp experts Macro procedures are evaluated at compile time (ie when read in by the compiler). Their arguments (if they have any) are bound to the following 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, *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. -- Syntax Procedures -------------------------------------------------- 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 sytax words. -- Active variables --------------------------------------------------- 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 -- WITH_PROPS and WITH_NARGS ------------------------------------------ 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 the 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. -- Nested procedure definitions --------------------------------------- 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 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 -- Making the procedure name a lexical identifier --------------------- The name may be declared as lexical or non-lexical, the default being non-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. -- Making the procedure name global to 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 .... -- Making a procedure name a procedure identifier --------------------- 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); lvars list; hd(tl(list)) enddefine; define test(list); lvars list; foo(list) enddefine; test([a b c]) => ** b 999 -> foo; test([a b c])=> ;;; MISHAP - ENP: EXECUTING NON-PROCEDURE ;;; INVOLVING: 999 ;;; DOING : mishap 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); lvars list; hd(tl(list)) enddefine; define test(list); lvars 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. 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 the value may already have been compiled, and redefining it so as to allow a different type of value will cause problems when that compiled code is run. Before re-defining such a procedure you will either have to leave Pop-11 and start again, or else first CANCEL the identifier. See HELP * CANCEL -- Making a procedure name a constant --------------------------------- 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; -- -- compile_mode See HELP * COMPILE_MODE for information on how to specify that all procedure names are automatically declared to be of type "procedure", or all of type "constant". -- Declaring local variables ------------------------------------------ As already indicated, input and output parameters default to non-lexical local variables. (Up to Poplog Version 15). This default can be over-ridden by declaring them locally as lexical, using LVARS or DLVARS, often with a gain in efficiency. However, if a variable X is declared as a lexical local of PROC then no procedure defined outside the textual scope of the definition of PROC can access X. This means that variables like CUCHAROUT, PRMISHAP, POPPROMPT, which may have to be accessible by other procedures must not be declared lexical locals. They can be localised using "dlocal", as described in HELP * DLOCAL. Generally local loop counters and other temporary information stores can be made lexical locals. The full semantics of lexical identifiers is explained in detail in REF *VMCODE in the section on 'Implementation of Lexical Scoping'. That is likely to be too advanced for most readers. See also HELP * LVARS, HELP * LEXICAL -- Restrictions on the use of lexical locals ------------------------ - Their values cannot be accessed during a break, e.g. if *POPREADY is called. (Mechanisms may be provided to overcome this later. See HELP * DEBUGGER ) - Their values cannot be accessed or updated via calls of VALOF. E.g. if W is a variable of any kind whose value is the word "popprompt" then valof(w) can always be used to access or update the value of "popprompt", a non-lexical POP-11 identifier. E.g. vars promptword; "popprompt" -> promptword; ** : 'More : ' -> valof(promptword); valof(promptword) => ** More : In general this sort of thing is not possible with lexical variables. - Lexical variables previously could not be used as settable pattern variables with MATCHES and procedures that invoke MATCHES, for example PRESENT, LOOKUP, REMOVE, FOREACH. See HELP *MATCHES, HELP *DATABASE However, since Poplog Version 15.0 a change has been available via the operator "!" (at Birmingham) which can be used as a pattern prefix to overcome these problems. See HELP * READPATTERN Previously lexical identifiers could occur after "^" and "^^" in lists, but not after "?" and "??", which prefix settable variables. However if "!" occurs to the left of the pattern this restriction is overcome. For more on this see TEACH * VARS_AND_LVARS -- Extending the define ... enddefine syntax, using define_form ------- It is possible for users to specify extensions to the syntax: define ... enddefine in order to use a format like define :myproctype .... enddefine Doing this first involves introducing the keyword "define_myproctype" as the name of a new form, as described in HELP * DEFINE_FORM. This would be introduced via the syntax form: define :define_form myproctype ... The help file explains the generic mechanism. It is used to specify several define_forms used in the Poplog libraries, e.g. define :for_extension (HELP * FOR_FORM) define :inline (HELP * INLINE) define :prolog (HELP * DEFINE_PROLOG) define :runtime_action (LIB * DEFINE_RUNTIME_ACTION) See REF * sys_runtime_apply define :ved_runtime_action (LIB * DEFINE_VED_RUNTIME_ACTION) See REF * ved_runtime_apply The following may be available in your Poplog library define :class (HELP * OBJECTCLASS) define :ruleset (HELP * POPRULEBASE (supersedes NEWPSYS)) -- Alternative formats for procedure definitions ---------------------- For reasons of compatibility with earlier POP11 or POP2 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. -- Archaic forms ------------------------------------------------------ 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; -- LIB POP2 ----------------------------------------------------------- Makes available some syntactic forms and procedures that were previously available in POP2. 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. -- Archaic form for output locals ------------------------------------- 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 => x y; -- RELATED DOCUMENTATION ---------------------------------------------- The following HELP files provide more information on procedures and procedure definitions. *OPERATION, *MACRO, *SYNTAX, - types of procedure identifiers *UPDATER, *UPDATEROF - on the form and use of procedure updaters *PROCEDURE - for use of POP-11 syntax words PROCEDURE ... ENDPROCEDURE *PDPROPS - information associated with the procedure *PDNARGS - the number of arguments required *DLOCAL - local variables and dynamically local expressions *VARS, - Use and declaration of dynamically scoped variables *LVARS, *LEXICAL REF *SYNTAX/lconstant - for use and declaration of lexically scoped variables and constants *CONSTANT, REF * SYNTAX/lconstant - use of constant identifiers See also: REF * PROCEDURE for information about the form of procedure records and procedures which operate on procedures. TEACH * PRIMER Some more advanced features for controlling the mode of compilation. REF * POPCOMPILE/pop11_define_declare HELP * COMPILE_MODE Original file: --- C.all/help/define ---------------------------------------------- --- Copyright University of Sussex 1987. All rights reserved. ------ --- $poplocal/local/help/define --- Copyright University of Birmingham 1997. All rights reserved. ------