rAO7uA}4G$:i"N0S+37P%cS, G|Ys7PS;$\X[0prk7P!)!"{| #7PeVd\`ti7P{Apparently the similarity of action between all the various CATCHs will have to be spelld out: basically they are of the form ( . . . ) Each catch-like operation push's a structurally-identical frame, in order to "protect" the computations e1 to en; the break-up of that frame may or may not involve other clean-up actions. Let "BUF" stand for the action of breaking up a frame. Then catcher | action on normal exit | action on abnormal exit name | (successful e1 to en) | ("throw" with a tag, and "errors") ___________________________________________________________________________ CATCH BUF search thrown-tag in "info" list, BUF if match found. CATCHALL BUF BUF, and apply "clean-up" function CATCH-BARRIER BUF search thrown-tag in "info" list, BUF if match found, error if not PASS-THRU BUF, run clean-up form run clean-up form, continue throwing ERRSET BUF if error exit, then BUF; if throw, then continue throwing (True, adherence to conventional tag names permits the whole mechanism of ERRSET to be macroified into forms with CATCHALL, but these features are not intended to be 100% independent.) In fact, a tag-list given to CATCH or CATCH-BARRIER is, except for the context of unseen-throw-tag errors, just like a function for catchall (lambda (tg val) (cond ((memq tg tag-list) val) ((*throw tg val)))) Also, PASS-THRU could just as easily take a function of no args, as CATCHALL take a function of two args, and the open-compilation would be the same. E.g. (PASS-THRU '(LAMBDA () (RETREAT)) (DO THIS) (THEN THAT)) rather than (PASS-THRU (RETREAT) (DO THIS) (THEN THAT)) or [choke!] (PASS-THRU '(RETREAT) (DO THIS) (THEN THAT)) However, the format ( . . . ) displays the nice syntactic symmetry of these functions, and the table displays the operational variations for each one. UNWIND-PROTECT doesn't preserve this symmetry, although it preserves another notion, that of left-to-right program evaluation. Note however that CATCHALL doesn't fully preserve the sense of left-to-right evaluation, since the clean-up function can be run after some of the forms have successfully completed. Regardless of which property one prefers to conserve, it seems useful to macroify the other one for use by the other conservationist camp. LThis is the general theory of LAMBDA-binding and variable scoping for S-1 NIL (and, we hope, NIL in general). It is expressed first descriptively, then operationally. The operative semantics given here are not the only ones which will work. The syntax of a LAMBDA-expression is as follows: (LAMBDA ) Each declaration (there may be more than one before the body is of the form (DECLARE ). The possible are not of interest here, except one: (SPECIAL ... ) which will have a bearing on the scoping of variables. The body consists of one or more forms to be evaluated in turn when the function denoted by the LAMBDA-expression is invoked. The value of the last one becomes the value of the invocation. The is a list of items. Each item is either a keyword or a parameter specification. A keyword is a symbol the first character of whose print name is "&". The valid keywords are: &OPTIONAL, &REST, &AUX ("kind") &SPECIAL, &LEXICAL ("specialness") &FUNCTION, &VARIABLE ("context") When a keyword appears as a item, it is "sticky"; that is, it applies to all following variable specifications until overridden. It is convenient for descriptive purposes to introduce two more pseudo-keywords here. They are NOT valid keywords for use in a ; they are for expository purposes only. They are: &REQUIRED ("kind") &UNKNOWN ("specialness") Using these, we can say that the default interpretation of a parameter specification will be &REQUIRED &UNKNOWN &VARIABLE. Scanning the parameter list from left to right, the default interpretation is modified by the appearance of sticky keywords. Sticky keywords can appear anywhere in the subject to these constraints: [1] The last item in the may not be a keyword. [2] &OPTIONAL, &REST, and &AUX may appear at most once each. &OPTIONAL may not follow &REST or &AUX; &REST may not follow &AUX. [3] If &REST appears, and more than one parameter specification follows it, then &AUX must appear somewhere after the first such and before the second such. That is, if &REST appears, then there must be exactly one &REST parameter. A parameter specification has the following syntax: ::= | ( ) | ( )# ( )# ::= | ( ) ::= | ::= &SPECIAL | &LEXICAL | &FUNCTION | &VARIABLE The keywords which appear in a parameter specification are un-sticky; they apply only to that parameter, overriding the defaults but not affecting them for other parameters to the right of this one. Note that a "kind" keyword may not be used as an un-sticky keyword. Also, the alternatives marked "#" may not be used unless the default interpretation of "kind" is &OPTIONAL. Conversely, if the "kind" is &OPTIONAL and no is present, the is assumed to be (). It is actually not permitted for a parameter to be &UNKNOWN. If the interpretation of a parameter, after processing all relevant keywords, is still &UNKNOWN, then it must be disambiguated according to the following rules: [1] If a parameter of the same name with the same "context" has appeared in a binding or SPECIAL declaration in a place lexically surrounding this parameter specification (and SPECIAL declarations in this LAMBDA expression are considered so to surround it), then the innermost one shall rule, except that &SPECIAL bindings are IGNORED; that is, the innermost SPECIAL declaration (not binding) or &LEXICAL binding rules. [2] Failing that, if the symbol which is the parameter name has a non-null SPECIAL property, then the binding shall be &SPECIAL. [3] Otherwise the binding shall be &LEXICAL. Once all this has been determined, then the following rules determine the processing for matching arguments to parameters: [1] First arguments are used, one by one, to satisfy corresponding &REQUIRED parameters. If there are not enough arguments, it is an error. [2] Any remaining arguments are matched left-to-right with any &OPTIONAL parameters. If any such &OPTIONAL parameter has an extra , then T is matched as a value for such parameters. (These parameters identify whether an argument was supplied for the &OPTIONAL parameter.) [3] If there are any &OPTIONAL parameters remaining which did not have arguments matched to them, then their s are evaluated in order and the parameters matched to the resulting values. Each is evaluated in an environment such that all preceding parameters in the are visible to the , but tnot the current parameter nor following ones. If any such parameter has an extra , then () is matched as a value to that parameter. [4] If there is no &REST parameter, then it is an error iff there are any arguments yet unmatched. If there is a &REST parameter, then it is matched to a vector containing all remaining arguments (possibly zero of them) in order. At each step it is an error if a non-functional value is matched to a &FUNCTION parameter. (This implies that the extra s for an &OPTIONAL, as well as &REST parameters, should not be of "context" &FUNCTION.) Now come the rules for evaluating symbols. There are two "evaluation contexts" in the NIL language: variable and functional. The two evaluation contexts are identical except in the treatment of symbols. Whenever a form is to be evaluated, it is evaluated in the variable context except in two circumstances: [1] The first position of a combination (e.g., in (CAR FOO), CAR is evaluated in the functional context, and FOO in the variable context). [2] The argument to the FUNCTION special form (e.g. in (FUNCTION FOO), FOO is evaluated in the functional context). The two contexts are completely distinct. Each is subdivided in the same way by the &SPECIAL/&LEXICAL distinction. A restriction on the &FUNCTION context is that values of variables in this context can only be valid procedural objects. When a symbol is to be evaluated in one or the other context, the value to use is determined as follows: [1] If a binding or SPECIAL declaration lexically surrounds the occurrence of the symbol, then the most recent such rules. If it is a SPECIAL declaration, then the special value is used. If it is a binding, then if the binding was &SPECIAL, then the special value is used (which may not be the value associated with that binding!); otherwise the lexical value is used (which will in fact necessarily be the one associated with that binding). [2] Failing that, look for a non-null SPECIAL property on the symbol. If found, then use the special value. If not, use the special value anyway, but perhaps give an error, as this is an anomalous situation. One way to implement this in an interpreter is as follows. Let there be two a-lists ENV and FNENV, one for each context. Let there be two objects and , which are internal to the interpreter and so cannot be the value of user variables. Then these rules apply. For applying a function: [1] Locate all SPECIAL declarations which precede the body. For each variable appearing in such a declaration, push the pair (variable . ) onto ENV. [2] To disambiguate an &UNKNOWN parameter, look back up the appropriate a-list for a pair with the same name, but ignore a pair with the marker. If one is found, then the parameter is &SPECIAL iff the pair has the marker. If not, then look for the SPECIAL property on the symbol, etc. [3] As each parameter is matched to a value, if the parameter is &LEXICAL then push the pair ( . ) onto the appropriate environment a-list; otherwise push ( . ) and use PROGV to create a special binding. For looking up a variable: Look in the appropriate environment using ASSQ. If a pair is found, use the corresponding value unless the or marker is there, in which case use SYMEVAL or FSYMEVAL as appropriate. Otherwise look for the SPECIAL property, etc. Of the many replies so far to a system-message quest, only one had any reservations about the following idea (apart from the obvious fear of breaking old code that uses this syntax in another way.) The idea primarily is to eliminate inconsistences with all the other argument formats, and to provide a means whereby DEFMACRO is the primitive macro-defining feature, just as DEFUN is the primitive function-defining feature. 1) DEFUN has the syntax ( ... ;required arguments, possibly none &OPTIONAL ;optional arguments, 3 formats: ; must be a symbol, or ( ) ; or, ( ) ; must be a symbol &REST ; must be a symbol ) and this covers all possible cases. There is no format for DEFUN like (DEFMACRO FOO (A1 A2 . B) ...), and I'd like to see that eliminated, since (DEFMACRO FOO (A1 A2 &REST B) ...) works the same. Then, exactly the same format as DEFUN has will cover all cases of DEFMACRO usages, except for the "whole form" case, and there will no longer be the (current) inconsistencies in what it means to be an "argument list". Since there is no equivalent in DEFUN for "whole form", why not use the case where the "argument list" is a symbol to mean that for DEFMACRO? Destructuring is accomplised with LET, anyway. [If the LISPM doesn't have a destructuring LET yet, there is no excuse, since the maclisp one will work ok there.] 2) DEFMACRO is probably a better primitive for macro definition than DEFUN, with all the hairy patchwork add-ons to DEFUN [witness the confusion over the declaration "(MACROS NIL)"]. And somewhere along the line, someone must have thought "MACRO" was preferred name for the macro-defining primitive, rather than DEFUN. So why not standardize on DEFMACRO?  Why MAPC and Friends Should Be Special Forms In the discussion below, an instance of use of MAPC is taken as prototypical of a problem which arises for all the MAP series, and possibly for other systemic functions, so read "MAPC" to mean any of this class. Consider, now, (MAPC (FUNCTION (LAMBDA (X) (GRUMBLE X Y))) LL) LISP compilers generall transform this into the equivalent of (DO-NAMED ((g0001 LL (CDR g0001))) ((NULL g0001) LL) T ((LAMBDA (X) (GRUMBLE X Y))) (CAR g0001)) ) Using DO-NAMED insures that GO's and RETURN's inside this functional argument do not get "coopted" by the code which the macro wrapped around the application of that function. By definition, we take that macro-expansion as the "correct" interpretation (i.e., meaning) for MAPC. Why not, then, let the LISP interpreter do this too? Well the basic reason is economy - its just too slow to go around macro-expanding all the time (but would you believe that LISP370 does just this?). How, then, does an interpreter then mimic this behaviour? Let us consider, first, the MACLISP/LISPM approach, namely MAPC is built as a SUBR (LSUBR for MACLISP) which the interpreter calls blindly (that is, without any special treatment, as would be the case with "special forms"). A flaw arises over the appearance in the example below of "Y" - consider two possible lexical contexts for example: 1) (DEFUN FOO (Z LL) (MAPC (FUNCTION (LAMBDA (X) (GRUMBLE X Y))) LL) (BAR Z LL) ) 2) (DEFUN FOO (Y LL) (MAPC (FUNCTION (LAMBDA (X) (GRUMBLE X Y))) LL) (BAR Y LL) ) When there are no lexically-local variables to worry about, as in case 1 above, then the situation is essentially that of MACLISP: the functional argument to MAPC is left as an "opener" (i.e. it is not turned into a closure) and at the time of application of that function, in the part of the interpreter that is running MAPC, you simply take whatever SPECIAL-variable environment is then in effect. The only problem that could arise is if the variable "Y" (from the example) happens to be the same as some variable used in the interpreter itself, say SI:FORM-TO-EVAL, and then there would be a "shielding" of the user's binding of "Y" by the code of the interpreter. But this case should be rare (and arcane). The MACLISP/LISPM solution is adequate for context 1 above, since the scoping indicates that the occurence of "Y" must be a SPECIAL variable reference. What about for 2? Well, since the MACLISP/LISPM interpreters make all variables special, 2 becomes an indistinguishable case of 1. But what if you do have an interpreter which does correctly mimic the behaviour of lexically-local variables? How does it handle the two cases? A SCHEME-style solution is to prohibit all "openers", and require that the first argument to (the SUBR version of) MAPC be a CLOSURE; but in general (except possibly on LISPM) consing up a closure each time to ** interpret ** the MAPC would be unduly costly (compilation is another matter). Another problem with this approach is deciding just which variables over which to close. In the lexical-only version of SCHEME, there is no problem, since ** all ** variables are closed over, and in the compiled version of FOO, there is no real cost involved (question: was there ever intended to be an interpreter for lexical-only SCHEME?). In a more flexible version of SCHEME (one in which dynamic bindings are permissible), the variables over which to close would be all those in the same lexical contour as the MAPC plus any other special variables of the interpreter which might "squeeze in between" the contour of the MAPC and that of the actual application of the closure. An optimization of this approach might be to limit access to the package on which the interpreter resides, and thereby no user code would inadvertently clash with any of the interpreter's variables; this would also prohibit the interpretation (** by that interpreter **) of just about any part of the interpreter. The real solution is not to let MAPC be defined as a subr; thus it is not required to "take evaluated arguments" (one argument of which would have to be a closure/flexure to get fully-correct interpretation). The NIL semantics forces the interpreter to recognize the points at which the lexical scoping changes; if MAPC is "special", then the interpreter can, without changing lexical contours, obtain the first "argument" as an "opener" and continue operations still in that original lexical contour, including the applications of the "functional" argument. If a user should want to override the system-supplied MAPC with a version of his own, ** and still retain the capability for correct interpretation **, then he would have to define a macro for MAPC which, say, expands like (MAPC (FUNCTION (LAMBDA (X) (GRUMBLE X Y))) LL) ==> (SUBR-MAPC (FLEXURE '(LAMBDA (X) (GRUMBLE X Y)) ...) LL) Recall that FLEXURE is is CLOSURE with all lexically-apparent automatically included. < Nomenclature Rules for Generic Sequence Functions in NIL 1) "foo" is the basic function, using EQUAL as predicate. They are 1a) POSITION - find the index in a sequence of the first occurrence of an element "equal" to a particular item. 1b) SKIP - find the index in a sequence of the first occurrence of an element not "equal" to a particular item. 1c) SEARCH - find the index in a sequence of the first occurrence of a subsequence which is element-by-element "equal" to a particular (the other argument) sequence. 1d) MISMATCH - find the index in a sequence of the first place whereat its elements are not element-by-element "equal" to a particular (the other argument) sequence. 1e) REMOVE - construct a new sequence from a given sequence, but not including any item "equal" to a given item. 1f) "DRAMMP" - A series of classic "Q-sequences" (or "list) handling functions: DELETE, DELASSOC, RASSOC, ASSOC, MEMBER, MEMASSOC, and POSASSOC. 2) To get the name for a function like "foo", but using "EQ" as the predicate, identify a "root" part of the name, and suffix a "Q" to it. For the "root": 2a) Take the first three letters of the basic name above. Thus we get DELQ, REMQ, ASSQ, MEMQ, and POSQ 2b) Take the whole name. Thus SEARCHQ, SKIPQ, and MISMATCHQ 2c) Exceptions, involving a primitive root prefixed by a syllable: DELASSQ, RASSQ, MEMASSQ, and POSASSQ 3) To get the name for a function like "foo", but taking an additional argument, the user-supplied "equality" predicate: 3a) Merely use the "root" as described under 2 above: Thus, POS, ASS, MEM, and DEL; and DELASS, RASS, MEMASS, and POSASS 3c) Drop the "CH" at the end of the name: Thus, SEAR, and MISMAT. 3d) Exceptions to the rule: Thus, SKP.