The Revised Maclisp Manual | Page A-5 | ||||||
|
|
Comments |
COMMENT | Special Form | (COMMENT ...) |
Definition:
(DEFUN COMMENT FEXPR (NIL) 'COMMENT)
;Usage of COMMENT (defun foo (x) (cond ((null x) 0) (t (comment x has something in it) (1+ (foo (cdr x))))))
Using the COMMENT special form causes the s-expression comment to be part of the code for the function. This means that (COMMENT ...) can only be used where its value can be ignored. For example:
(+ 3.14159 (COMMENT PI) X) ;a bad usage of COMMENT
Web-Only Note:
The name PI is not a pre-defined name in MACLISP.
But in other places, such as
(progn (comment Call the function F) (f) (comment Call the function G) (g))
it is acceptable.
More commonly, however, code is commented using the semicolon readmacro feature of the standard READ syntax. A semicolon causes the rest of the line to be ignored by READ. For example,
(+ 3.14159 ;pi X)
Will read just like (+ 3.14159 X) would.
Conditionals |
COND | Special Form | (COND clause1 clause2 ...) |
Processes its arguments, called "clauses," from left to right. The CAR of each clause, called the "antecedent," is evaluated. If it is NIL, COND advances to the next clause. Otherwise, the CDR of the clause is treated as a list of forms, called "consequents," which are evaluated from left to right. After evaluating the consequents, COND returns without inspecting any remaining clauses. The value is the value of the last consequent evaluated, or the value of the antecedent if there were no consequents in the clause. If COND runs out of clauses (i.e. if every antecedent is NIL), the value of the cond is NIL. e.g.,
(cond ((zerop x) (+ x 3)) ;first clause. ;(zerop x) is antecedent. ;(+ x 3) is consequent. ((null y) (setq x 4) (cons x z)) ;a clause with 2 consequents (z) ;a clause with no consequents. ;the antecedent is just z. ) ;this is the end of the cond.
This is like the traditional LISP 1.5 cond except that it is not necessary to have exactly one consequent in each clause, and it is permissible to run out of clauses.
(cond ((zerop 3) 'nope) ((oddp 7) 'yep) (t 'better-not-get-here)) => YEP (cond ((zerop 3) 'still-no) ((oddp 6) 'nope)) => NIL ;The COND `fell off the end' and returned NIL. (cond ((zerop 0) 'zero-is-zero) ((= 1 1) '1-equals-1) (t 'randomness)) => ZERO-IS-ZERO ;Uses the first succeeding test only
IF | Special Form | (IF test consequent alternative1 alternative2 ...) |
The test is evaluated. If it returns true (anything other than NIL), the consequent is evaluated and its result is returned. If the test return NIL, the alternatives are evaluated from left to right, returning the value returned by the last of them, or NIL if there are none.
Web-Only Note:
Amazingly, documentation of this macro was omitted from the hardcopy version of this manual.
Example:
(if t 1 2) => 1 (if nil 1 2) => 2 (if t 1 2 3) => 1 (if nil 1 2 3) => 3 (if t 1) => 1 (if nil 1) => NIL
(do ((list '(t nil) (cdr list))) ((null list)) (print (car list)) (if (car list) (print 1) (print 2) (print 3))) T 1 NIL 2 3 => NIL
NIL | Concept | False |
CASEQ | Special Form | (CASEQ obj clause1 clause2 ...) |
Similar to COND, though much more constrained and in theory capable of being compiled better. Clauses have the form
(match form1 form2 ...)
They are tried successively in much the same way as are COND clauses except that the match is not an arbitrary expression. Instead it is an unevaluated list of objects. If any of the objects in the match part is the same as the value to which obj evaluated, the forms are executed and the value of the last form in that clause is returned from the CASEQ. It is an error if obj is not supplied. Sameness is determined in a constrained way -- all objects in all matches must be of the same type -- either all symbols or all fixnums. The appropriate comparison operator will be selected on the basis of their type. A T instead of a list of things to check against specifies the `else' clause. A further shorthand allowed is that a list one long may be replaced by its car. e.g.,
(CASEQ NUMBER ((1) ...) ((2) ...))
is the same as
(CASEQ NUMBER (1 ...) (2 ...)).
However, since T is reserved for the else clause, it must be put in a list in order to be checked for. e.g.,
(CASEQ FOO ((T) result1) (T result2))
returns result1 if FOO evaluates to the symbol T and result2 otherwise.
A common error made by novices is to forget the definition and assume that CASEQ will evaluate the match part of the clause. People have been known to write:
(CASEQ X ('A 'B) (T 'C)) ;a bad example
Expecting that if X is eq to A, CASEQ will return B. In fact, it will, but not for the reason one might expect. Remember that 'A is shorthand for (QUOTE A), so this form is identical to:
(CASEQ X ((QUOTE A) 'B) (T 'C)) ;a bad example, continued
This says that if X is one of either QUOTE or A, then return B; in most cases, this is not the intent. In fact, if he did mean that, the programmer would be better off writing:
(CASEQ X ((A QUOTE) 'B) (T 'C)) ;a bad example, concluded
Multics users must (%include other_other) to get CASEQ.
Examples:
(CASEQ 3 ((3 4) 'WIN) (T 'LOSE)) => WIN
(CASEQ 2 ((3 4) 'LOSE) (T 'WIN)) => WIN ; otherwise
(CASEQ 2 ((3 4) 'LOSE)) => NIL
(CASEQ 2 ((A) 'LOSE) (T 'WIN)) => error! ;type doesn't match
(CASEQ 'E ((A B C) 'X) ((D E) 'Y) (T 'Z)) => Y
(CASEQ 'D ((A B C) 'X) (D 'Y) (E 'Z)) => Y
(CASEQ 'T ((A B C) 'X) ((T) 'Y) (T 'Z)) => Y
(CASEQ 'Q ((A B C) 'X) (T 'Y) ((T) 'Z)) => Y
(CASEQ 'Q ((A B C) 'X) ((T) 'Y) (T 'Z)) => Z
(CASEQ 'QUOTE ('A 3) (T 4)) => 3 ;think!
SELECTQ | Special Form | (SELECTQ obj clause1 clause2 ...) |
SELECTQ is a conditional which chooses one of its clauses to execute by comparing the value of a form against various constants, which are typically keywords of some sort.
The form of a clause is:
(match form1 form2 ...)
SELECTQ clauses are treated something like COND in that they are tried successively until one succeeds and then its value is returned. The difference is that rather than an arbitrary condition for each clause, the match part of a SELECTQ is not an evaluable expression at all. Instead it is an unevaluated list of objects. A clause will be selected if obj is a number and it is the first clause whose match list contains a number which is numerically equal, or if obj is anything else and some object its match list is eq to it. If a clause is selected, then the forms are executed, returning the result the last form (or NIL if there were no forms).
Either of the symbols T or OTHERWISE instead of a list of things to check against specifies the `else' clause.
There is a shorthand syntax for a match list containing exactly one element. If that element is atomic, it may be replaced by its car (except that T and OTHERWISE must be in a list if they are not intended to specify an `else' clause).
The definition of SELECTQ was taken from the Lisp Machine and is very similar to, but not interchangeable with, that of CASEQ. The primary distinction between CASEQ and SELECTQ is that the datatype of the object to match need not match the datatype of the objects specified as matches in the clauses. i.e., (CASEQ 3 (X 'X)) is not a valid CASEQ expression because 3 and X are not of the same type, but (SELECTQ 3 (X 'X)) is a valid SELECTQ expression because runtime datatype checking and selection of comparison operations will occur.
Multics users must (%include other_other) to get CASEQ.
Examples:
(SELECTQ 3 ((A 3) 'FOO) (T 'BAR)) => FOO
(SELECTQ 3 (3 'FOO) (4 'BAR)) => FOO
(SELECTQ 3 ((A 5) 'FOO) (T 'BAR)) => BAR
(SELECTQ 3 ((A 5) 'FOO) (OTHERWISE 'BAR)) => BAR
(SELECTQ 3 ((A 5) 'FOO) (C 'BAR)) => NIL
CASEQ | Style Note | (CASEQ ((X) ...)) |
Because of the special-case handling of T in CASEQ, and of T and OTHERWISE in SELECTQ, some programmers adopt the style of never using the shorthand format, reserving that syntax exclusively for the “else” case. In such a case, one would always write
(CASEQ ((X) ...)) rather than (CASEQ (X ...))
for the sake of notational consistency. There is no computational penalty for adopting such a style and some programmers consider the form with the extra parens to be more clear.
Logical Conjunction, Disjunction |
AND | Special Form | (AND form1 form2 ...) |
Evaluates its arguments one at a time, from left to right. If any argument evaluates to NIL, the AND immediately returns NIL without evaluating the remaining arguments. If none of the arguments evaluate to NIL, the AND returns the value of its last argument.
It is considered completely appropriate to rely on the left to right order of evaluation in AND. Here are some common tests that exploit that property:
(AND (NOT (ATOM X)) (CAR X)) or (AND (BOUNDP X) (SYMEVAL X))
In the first of these expressions, for example, we could not safely evaluate (CAR X) if we did not know that X is non-atomic. However, because AND is defined as it is in Lisp, this construction is well-defined to do the “right” thing.
Examples:
(AND T T NIL T) => NIL
(AND NIL T) => NIL
(AND T NIL) => NIL
(AND T T T T T) => T
(AND T 3.) => 3. ; discouraged (see style notes)
(AND) => T ; trivial case
OR | Special Form | (OR form1 form2 ...) |
Evaluates form1, form2, ... one by one from left to right. If a form evaluates to NIL, OR proceeds to evaluate the next argument. If there are no more arguments, OR returns NIL. But if an argument evaluates to something other than NIL, the OR immediately returns that value without evaluating any remaining arguments. OR can be used both for logical operations, where NIL stands for “false” and T for “true,” and as a conditional expression (see style notes for OR).
Examples:
(OR NIL NIL 3 NIL) => 3
(OR NIL NIL) => NIL
(OR (EVENP 3) (ODDP 4)) => NIL
(OR (GET 'FOO 'NOT-THERE) 'BAR) => BAR
(OR 'FOO (GET 'CAR 'SUBR)) => FOO
(OR) => NIL ;trivial case
Using AND and OR as conditionals |
AND | Style Note | AND vs IF |
Sometimes, AND may be seen in code used as a conditional to perform side-effects as in:
(AND X (PRINT X)).
Because of its guaranteed left to right nature, AND will reliably accomplish the side-effects as specified, but many programmers find such forms visually confusing and too many others have accidentally introduced bugs into their code by trying to be cute in this way. Writing such expressions using IF or COND instead is strongly recommended. For example, the above example is better written as one of:
(IF X (PRINT X)) or (COND (X (PRINT X))).
On the other hand, expressions like
(AND (NOT (ATOM X)) (EQ (CAR X) 'A)).
which rely on the order of evaluation of AND's arguments but which do not do side-effects are completely reasonable and often necessary. The above example might err if AND were allowed to do the EQ test first. Fortunately, AND is constrained to test the expressions from left to right.
Where sometimes with OR, we might write
(SETQ X (OR Y 3))
because we can read this aloud as “SETQ X to Y or 3,” we cannot similarly write
(SETQ X (AND Y 3))
and meaningfully think of X as being set to both Y and 3. Hence, the use of the value of AND as anything other than a truth value is mildly discouraged. Where the user might be inclined to write AND expressions such as this one, he should rewrite his code using IF or COND instead as in
(SETQ X (IF Y 3 NIL)) or (SETQ X (IF (NOT (NUMBERP Y)) Y)) or (SETQ X (COND (Y 3) (T NIL))).
so that it is easy for the reader to see what is going on.
OR | Style Note | OR vs IF |
(OR X (SETQ X 3)).
Because of its guaranteed left to right nature, OR will reliably accomplish the side-effects as specified, but many programmers find such forms visually confusing and too many others have accidentally introduced bugs into their code by trying to be cute in this way. Writing such expressions using IF or COND instead is strongly recommended. For example, the above example is better written as one of:
(IF (NOT X) (SETQ X 3)) or (SETQ X (OR X 3)).
On the other hand, OR is frequently used to retrieve a value without causing a side-effect, as in:
(DEFUN DISPATCHER (X DATA) (FUNCALL (OR (GET X 'FN) *DEFAULT-FN*) X DATA))
The FUNCALL here can be read as “FUNCALL either the object which is on the FN property of X, or the value of the variable *DEFAULT-FN*”, where things referred to at any point in the OR are preferred over the other things which come after. If no side-effects are going on in the body of the OR, this style is not discouraged in the way an analogous usage of AND would be.
Logical Negation |
NOT | Function | (NOT q) |
Examples:
(NOT NIL) => T
(NOT T) => NIL
(NOT '(A B C)) => NIL
(NOT (NOT 3)) => T ; (NOT (NOT x)) isn't x!
NOT | Style Note | NOT vs NULL |
Beginning programmers often have trouble deciding when to use NOT and when to use NULL, since the two are functionally equivalent. The answer lies in how the argument is being viewed. (NULL x) says “I expect x to be a list, and I would like to know if that list is empty.” while (NOT x) says “The object x could be any datatype; I only care to treat it as a boolean quantity and to invert its truth value.”
(do ((x '(a b c d) (cdr x))) ((null x)) (print x)) ;use of NULL (do ((a 3 (plus a 3.7))) ((not (< (sqrt a) 8.0))) (print a)) ;use of NOT
The LET Family |
LET | Special Form | (LET bvlspec . body) |
LET is used to bind some variables to some objects, and then to evaluate some forms (those which make up the body) in the context of those bindings.
A LET form looks like:
(LET ((var1 exp1) (var2 exp2) ...) bodyform1 bodyform2 ...)
When this form is evaluated, all the exps are evaluated. Then all the corresponding vars are bound to their respective results. Finally, the bodyforms are evaluated with these bindings in effect, the old values of the vars are restored, and the value that resulted from evaluating the last bodyform is returned as the value of the LET.
There are two shorthand formats for the (var exp). One is (var) and the other is just var. Both of these mean to bind var to NIL. As a good rule of style, you should use (var NIL) if you mean to use the value of var without assigning it first, and just var or (var) if you in fact do not care what var gets bound to. This will make it clearer to later readers of your code.
;; Some initializations for sake of our demo (setq foo 'foov bar 'barv foobar '(foox barx)) => (FOOX BARX) (let ((foo 3) (bar 4)) (+ foo bar)) => 7 (let ((foo bar) (bar foo)) (list foo bar)) => (BARV FOOV) ; Not (BARV BARV)!! foo => FOOV
It is permissible to place an s-expression pattern in place of a symbol in the (sym init) form of an element of the bvlspec. The symbols in the pattern will become bound to the matching elements of the initialization (see DESETQ). This kind of pattern-matched assignment is called destructuring.
(let (((a b) '(1 2)))
(cons b a))
=> (2 . 1)
Multics users must (%include destructuring_let) to get the destructuring features of LET.
LET* | Special Form | (LET* bvlspec . body) |
Same as LET but does bindings in sequence instead of in parallel. A LET* form looks essentially like that of a LET's:
(LET* ((var1 exp1) (var2 exp2) ...) bodyform1 bodyform2 ...)
However, in a LET*, the result of exp1 is bound to the var1 before the exp2 is evaluated; the exp2 is then bound to the var2 before the exp3 is evaluated; and so on. Finally, the bodyforms are evaluated from left to right, the bindings of the vars are restored, and the result of the last bodyform is returned.
The trivial cases of a LET* with 1 binding is the same as a LET. As a point of style, LET should be preferred for that case, since LET* should be reserved for cases where the reason for its existence (sequential binding) is being exploited.
(LET ((X 3)) (+ X X)) rather than (LET* ((X 3)) (+ X X))
Multics users must (%include destructuring_let) to get LET*.
DESETQ | Special Form | (DESETQ pat1 exp1 pat2 exp2 ...) |
Uses the same destructuring technique used by LET and LET* but causes an assignment rather than a binding. For example,
(DESETQ (A (B . C)) '(1 (2 3 4) 5 6))
will give A a value of 1, B a value of 2 and C a value of (3 4). The (5 6) is ignored by the assignment.
It is an error for a non-atomic pattern to match a symbol other than NIL. e.g.,
(DESETQ (A . B) 'C) is an error!
But it is acceptable for a non-atomic pattern to match NIL, as in:
(DESETQ (X Y . Z) '(3)),
Iteration |
DO | Special Form | (DO bindings exit . body) |
The DO special form provides a generalized “do loop” facility, with an arbitrary number of associated bound variables.
DO has two basic syntaxes--the “old” and “new” styles. Neither style is in danger of being flushed from the language. The new style is encouraged, however, since some user programs for code manipulation may be confused by the older form.
;; The ``old-style'' DO (DO var init repeat endtest . body) ;; The ``new-style'' DO (DO (varspec1 varspec2 ...) (endtest . exitbody) . body) ;; where varspec is either a var ;; or (var [[init] repeat]).
The first form in a new-style DO form is a list of zero or more bound variable specifications. If a specification is simply a symbol, that symbol will be bound to NIL upon entry to the loop and will not be reassigned unless assigned by user code within the loop. If a specification is a list, the car of that list names a variable to be bound within the loop. If the list has only one element (the symbol), then that symbol is bound to NIL. If there are at least two elements in the list, then the second form is an expression to be evaluated -- the result of which will become the initial value for that variable within the loop. If there are three elements in a specification list, the third element is an expression which will be evaluated on each successive iteration of the loop; the result of that evaluation will become the new value of the variable for that iteration of the loop. Note that if the repeat value is omitted, the variable will only be reassigned if the user does the assignment explicitly from his code.
All assignment to the loop's bound variables is done in parallel. At the beginning of the first iteration, all the init forms are evaluated before any bindings are done. Then all the bindings occur in parallel. Note that the forms are evaluated before the loop variables become bound, so any references in the init forms to variables with the same name as loop variables will access variables bound in some outer dynamic contour if they exist, or will cause an unbound variable error.
At the beginning of each succeeding iteration, any repeat forms which were specified are evaluated and then the assignment of their associated bound variables occurs in parallel. Note that all the repeat forms are evaluated before any bound variables are changed, so any reference made in a repeat expression of some DO loop to a variable bound in that loop will refer to the value of the variable from the previous iteration (regardless of whether that variable's new value has already been computed).
The second form in a new-style DO form is a list of an expression, endtest, which tests for the end of the loop, and zero or more forms, called the exitbody, which are evaluated if the endtest returns T. At the beginning of each iteration (including the first), before entering the body, the endtest is evaluated. If the result is NIL, execution proceeds with the body of the DO and then returns to retry the repeat values and the endtest and so on.
If the result of evaluating the endtest is not NIL, the forms in the exitbody are evaluated from left to right and then DO returns the value returned from the last such evaluation or NIL if there were no forms in the exitbody.
As a special case, if the second form of DO is NIL, the body of the DO is executed exactly once. It is an error in this case for there to be repeat forms specified in the varspecs. This type of DO was introduced to allow users to achieve the functionality of a PROG but with initialization of variables to something other than NIL. If a RETURN is executed, the DO returns the value given in the RETURN. Otherwise, the DO returns NIL when it “falls off the end” as would a PROG. It is not clear why the implementors didn't just extend PROG in the obvious way to make it do the right thing, rather than perverting DO in this peculiar way. Many code-walking programs will likely not know about this special case of DO and users are encouraged not to use it. Cases of
(DO ((var1 val1) (var2 val2)) NIL . body)
can be mechanically re-written as
(LET ((var1 val1) (var2 val2)) (PROG NIL . body))
Cases of the old-style DO can be mechanically re-written by changing:
(DO var init repeat endtest . body)
into
(DO ((var init repeat)) (endtest) . body).
Note that in either old or new style do, the body is not required to contain any forms at all. If no forms appear, control returns immediately to the top of the loop. This case happens quite frequently. For example, the following loop which contains no body will reverse the elements of a list, L, in the same way as the REVERSE function would:
(DO ((L L (CDR L)) (RESULT NIL (CONS (CAR L) RESULT))) ;exploits parallel assignment ((NULL L) RESULT))
A DO body is like a PROG body. Any top level atoms are not evaluated, but rather are taken as tags. Control can be transfered to the point immediately after a given tag in the current DO or PROG level by use of the function GO. Also as with PROG, the function RETURN can cause a value to be immediately returned from the DO rather than waiting for exit via the endtest. It is, therefore, not uncommon to see code of the form:
(DO (...varspecs...) (NIL) ;Loop `forever' ... (IF ...test... (RETURN value)) ...)
;; Making a list of numbers from 0 to 9 (backwards) ;; Note that this loop needs no body. (do ((i 0 (1+ i)) (x nil (cons i x))) ((= i 10.) x)) => (9. 8. 7. 6. 5. 4. 3. 2. 1. 0.) ;; Making the same list forward (do ((i 0 (1+ i)) (x nil (cons i x))) ((= i 10.) (nreverse x))) => (0. 1. 2. .3. 4. 5. 6. 7. 8. 9.) ;; Only the odd numbers (do ((i 0 (1+ i)) (x nil)) ((= i 10.) (nreverse x)) (if (oddp i) (push i x))) => (1. 3. 5. 7. 9.) ;; We could have done this using no body, but the resulting ;; code wouldn't have been as easy to read... (do ((i 0 (1+ i)) (x nil (cond ((oddp i) (cons i x)) (t x)))) ((= i 10.) (nreverse x))) => (1. 3. 5. 7. 9.) ;; Reversing a list ;; Generally, using the same variable name for two things ;; as shown here is discouraged, but we offer this example ;; to illustrate what would happen if you tried ... (let ((x '(a b c d))) (list (do ((x x (cdr x)) (result nil (cons (car x) result))) ((null x) result)) x)) => ((D C B A) (A B C D))
PROG/GO/RETURN |
PROG | Special Form | (PROG bvl s1 s2 ...) |
This is a standard lisp PROG. bvl is a list of symbols to be bound to NIL. s1, s2, ... are looked at from left to right -- atoms are ignored (not evaluated), but serve as tags to GO to (see documentation on GO). If PROG runs out of forms to evaluate, NIL is returned. Only by execution of a RETURN may a PROG be exited with a value other than NIL. Note also that RETURNs are intended to be lexical in nature --- although returning from a PROG from a function called within that PROG will work in the interpreter, it will not and should not be expected to compile.
;; Using PROG/GO to implement an iterative FACTORIAL (defun factorial (x) (prog (i n) (if (minusp x) (error "Negative argument to FACTORIAL" x)) (setq n 1) (setq i x) lp (if (zerop i) (return n)) (setq n (times n i)) (setq i (sub1 i)) (go lp))) => FACTORIAL (factorial 5) => 120. ;; The following example illustrates the use of PROG to implement ;; a more sophisticated control construct... (defmacro retry-if-error (&body body) `(prog () top (errset (return (progn ,@body)) nil) (go top))) => RETRY-IF-ERROR (retry-if-error (factorial (read))) -4 4 => 24. (retry-if-error (factorial (read))) 3 => 6.
PROG | Style Note | PROG vs LET, DO, ... |
PROG was one of the earliest primitives available for accomplishing looping in Lisp. In those days, it was quite common.
Many programmers today tend to avoid PROG because they feel it is overly general for most applications. Other primitives have since been added which offer better expressive power for most applications. In most cases, uses of PROG are better re-written using LET and LET* to accomplish binding, or using DO to achieve a looping effect.
For example, consider the code fragment:
(PROG (ENTRY VALUE) (SETQ VALUE (MICRO-EVAL FORM A-LIST)) (SETQ ENTRY (ASSOC VARIABLE A-LIST)) (COND (ENTRY (SETF (CDR ENTRY) VALUE)) (T (SETQ ENTRY (LIST VARIABLE VALUE)) (NCONC A-LIST (LIST ENTRY)))) (RETURN VALUE))
Using LET*, this can be re-written more naturally as:
(LET* ((VALUE (MICRO-EVAL FORM A-LIST)) (ENTRY (ASSOC VARIABLE A-LIST))) (COND (ENTRY (SETF (CDR ENTRY) VALUE)) (T (SETQ ENTRY (LIST VARIABLE VALUE)) (NCONC A-LIST (LIST ENTRY)))) VALUE)
Consider an iterative example:
(PROG (PL) (SETQ PL (PLIST SYM)) TOP (COND ((NULL PL) (RETURN NIL)) ((EQ (CAR PL) IND) (RETURN (CADR PL)))) (SETQ PL (CDDR PL)) (GO TOP))
This can better be expressed using DO, as in:
(DO ((PL (PLIST SYM) (CDDR PL))) ((NULL PL) NIL) (IF (EQ (CAR PL) IND) (RETURN (CADR PL))))
RETURN | Special Form | (RETURN val) |
RETURN is used to return from a PROG or a DO. The argument, val, is returned by the innermost lexically apparent PROG or DO as its value. If no PROG or DO is lexically apparent, the result is undefined. (The compiler will be unable to correctly compile the code; the interpreter will do something interesting, but not something which is semantically well-formed.)
Note: System break loops look for the special case of a form whose car is the symbol RETURN and do something with such forms. That behavior is in no way related to the RETURN function in spite of the syntactic similarity (see BREAK).
Example:
(PROG (X) (SETQ X 3) (RETURN X) (PRINT 'NEVER-GETS-HERE)) => 3
GO | Special Form | (GO tag) |
The GO special form is used to do a transfer of control within the body of a DO or a PROG. If the form in its argument position is an atom, it is not evaluated; otherwise it is evaluated and the result of the evaluation will be used instead. The GO transfers control to the point in the PROG labelled by a tag which is EQ to the tag.
If there is no such tag in the PROG, an UNSEEN-GO-TAG error will occur. If this error occurs and you would like to continue execution of the program by transferring to some other tag, goodtag, the default error handler will allow you to type (RETURN '(goodtag)) to the error breakpoint in order to proceed.
(PROG (X Y Z) (SETQ X something) LOOP ...do something... (IF ...predicate... (GO LOOP)) ;regular go ...do more stuff... (GO (COND ((MINUSP X) 'LOOP) ;`computed' go (T 'ENDTAG))) ENDTAG (RETURN Z))
Note: The compiler is not notably good at generating good code for computed GO's. Code may occassionally be more compact using computed GO's. Usually, however, it will be clearer and more efficient if one writes:
(COND ((MINUSP X) (GO FOO)) (T (GO BAR)))
instead of
(GO (COND ((MINUSP X) 'FOO) (T 'BAR))).
GO | Style Note | Unstructured Transfer of Control |
Use of GO as a way of getting around in programs is strongly discouraged. There are many more structured control mechanisms which lend a far more readable style to programs. Coding using GO is basically a stylistic throwback to the days of line-oriented programming languages such as Fortran and COBOL.
LOOP Iteration Macro |
LOOP | Special Form | (LOOP . loopbody) |
LOOP is a programmable iteration facility used in Maclisp, NIL and Lisp Machine Lisp which was inspired by, but is not compatible with,the “FOR” facility in Interlisp's CLISP.
The general approach is that a LOOP form macroexpands into a single program loop, into which a variety of features can be incorporated. The loop consists of some initialization (prologue) code, a body which might be executed any number of times, and some exit (epilogue) code. The loop can have local variables. The kinds of features offered are, for example, initialization and stepping of local variables, deciding when to end the iteration, putting user written code into the body, stepping variables through “paths” in data structures (not described in this draft), accumulating quantities in local variables, and returning a value from the LOOP.
Internally, LOOP constructs a PROG which includes variable bindings, pre-iteration (initialization) code, post-iteration (exit) code, the body of the iteration, and the stepping of the variables of iteration to their next value (which happens on every iteration after executing the body).
A clause consists of a keyword symbol and any keywords which it deals with. For example,
(LOOP FOR X IN L DO (PRINT X))
contains two clauses, “FOR X IN L” and “DO (PRINT X).” Certain of the parts of the clause will be described as being expressions. For example, (PRINT X) in the above. An expression can be a single Lisp form or a series of forms implicitly collected by a PROGN. An expression is terminated by the next atom (which is taken as a keyword) or by the end of the LOOP body. This syntax allows only the first expression to be atomic, but makes misspelled keywords more easily detectable.
Here are some sample programs written using LOOP:
;;; This function takes an association list as an argument and ;;; returns a list of the keys. e.g., ;;; (GATHER-ALIST-ENTRIES '((FOO 1 2) (BAR 259) (BAZ))) ;;; => (FOO BAR BAZ) (defun gather-alist-entries (list-of-pairs) (loop for pair in list-of-pairs collect (car pair))) ;;; This function takes a one-D, type FIXNUM array and returns ;;; the maximum element of that array. (defun find-maximum-element (an-array) (loop for i from 0 below (array-dimension-n 1 an-array) maximize (arraycall fixnum an-array i)))
The discussion of LOOP in the next several pages is only a summary of LOOP's features. Descriptions of certain features have been omitted and descriptions of others have been shortened for this draft; a later draft may contain a more full description. For more complete information in the meantime, see MIT Laboratory for Computer Science TM-169, “LOOP Iteration Macro.”
Multics users must (%include loop) to get LOOP.
The FOR keyword takes a variety of syntaxes. Each has a slightly different meaning; read their descriptions carefully. AS is a synonym for FOR.
FOR var [datatype] IN expr1 [BY expr2]
This iterates over each of the elements in the list expr1. If the BY subclause is present, expr2 is evaluated once on entry to the loop to supply the function used to fetch sublists instead of CDR.
FOR var [datatype] ON expr1 [BY expr2]
This is like the previous FOR format, except that var is set to successive sublists of the list instead of successive elements. Note that since var will always be a list, it is not meaningful to specify a datatype unless var is a destructuring pattern (beyond the scope of this draft). Note also that LOOP uses a NULL rather than an ATOM test to implement both this and the preceding clause.
FOR var [datatype] = expr
On each iteration, expr is evaluated and var is set to the result.
FOR var [datatype] = expr1 THEN expr2
var is bound to expr1 when the loop is entered, and set to expr2 (re-evaluated) at all but the first iteration. Since expr1 is evaluated during the binding phase, it cannot reference other iteration variables set before it; for that, use the following:
FOR var [datatype] FIRST expr1 THEN expr2
This sets var to expr1 on the first iteration, and to expr2 (re-evaluated) on each succeeding iteration. The evaluation of both expressions is performed inside of the LOOP binding environment, before the LOOP body. This allows the first value of var to come from the first value of some other iteration variable, allowing such constructs as
(LOOP FOR TERM IN POLY FOR ANS FIRST (CAR TERM) THEN (GCD ANS (CAR TERM)) FINALLY (RETURN ANS))
FOR var [datatype] FROM expr1 [TO expr2] [BY expr3]
This performs numeric iteration. var is initialized to expr1, and on each succeeding iteration is incremented by expr3 (default 1). If the TO phrase is given, the iteration terminates when var becomes greater than expr2. Each of the expressions is evaluated only once, and the TO and BY phrases may be written in either order. DOWNTO may be used instead of TO, in which case var is decremented by the step value, and the endtest is adjusted accordingly. If BELOW is used instead of TO, or ABOVE instead of DOWNTO, the iteration will be terminated before expr2 is reached, rather than after. Note that the TO variant appropriate for the direction of stepping must be used for the endtest to be formed correctly; i.e. the code will not work if expr3 is negative or zero. If no limit-specifying clause is given, then the direction of the stepping may be specified as being decreasing by using DOWNFROM instead of FROM. UPFROM may also be used instead of FROM; it forces the stepping direction to be increasing. The datatype defaults to FIXNUM.
The WITH keyword may be used to establish initial bindings, that is, variables which are local to the loop but are only set once, rather than on each iteration. The WITH clause looks like:
WITH var1 [datatype] [= expr1] [AND var2 [datatype] [= expr2]]...
If no expr is given, the variable is initialized to the appropriate value for its data type, usually NIL. WITH bindings linked by AND are performed in parallel (as in a LET); those not linked are performed sequentially (as in a LET*). That is,
(LOOP WITH A = (FOO) AND B = (BAR) ...)
does its bindings like
... (LET ((A (FOO)) (B (BAR))) ...) ...
whereas
(LOOP WITH A = (FOO) WITH B = (BAR) ...)
does its bindings like
... (LET* ((A (FOO)) (B (BAR))) ...) ...
INITIALLY expression
This puts expression into the prologue of the iteration. It will be evaluated before any other initialization code other than the initial bindings. For the sake of good style, the INITIALLY clause should therefore be placed after any WITH clauses but before the main body of the loop.
FINALLY expression
This puts expression into the epilogue of the loop, which is evaluated when the iteration terminates (other than by an explicit RETURN). For stylistic reasons, then, this clause should appear last in the loop body. Note that certain clauses may generate code which terminates the iteration without running the epilogue code; this behavior is noted with those clauses. Most notable of these are those related to aggregated boolean tests. This clause may be used to cause the loop to return values in a non-standard way, as in:
(LOOP FOR N IN L SUM N INTO THE-SUM COUNT T INTO THE-COUNT FINALLY (RETURN (QUOTIENT THE-SUM THE-COUNT)))
DO expression
expression is evaluated each time through the loop, as shown below. DOING is a synonym.
;;; This function prints each element of its argument list.
(defun print-elements-of-list (list-of-elements)
(loop for element in list-of-elements
do (print element)))
Some clauses say how to accumulate a return value for the iteration. The general form is
collectiontype expr [datatype] [INTO var]
Where collectiontype is a LOOP keyword and expr is the thing “being accumulated” somehow. If no INTO is specified, then the accumulation will be returend when LOOP terminates. If there is an INTO, then when the epilogue of the LOOP is reached, var (a variable automatically bound locally in the loop) will have been set to the accumulated result and may be used by the epilogue code. In this way, a user may accumulate and somehow pass back multiple values from a single LOOP, or use them during the loop. It is safe to reference these variables during the loop, but they should not be modified until the epilogue code of the loop is reached. For example:
(LOOP FOR X IN LIST COLLECT (FOO X) INTO FOO-LIST COLLECT (BAR X) INTO BAR-LIST COLLECT (BAZ X) INTO BAZ-LIST FINALLY (RETURN (LIST FOO-LIST BAR-LIST BAZ-LIST)))
COLLECT expr [INTO var]
This causes the values of expr on each iteration to be collected into a list. (COLLECTING is a synonym.)
NCONC expr [INTRO var]
This is like COLLECT, but the results are NCONCed together. NCONCING is a synonym. For example,
(LOOP FOR I FROM 1 TO 3 NCONC (LIST I (* I I)))
returns (1 1 2 4 3 9).
APPEND expr [INTRO var]
This is like NCONC, but results are APPENDed. APPENDING is a synonym.
COUNT expr [INTO var] [datatype]
If expr evaluates true, a counter is incremented. The datatype defaults to FIXNUM. COUNTING is a synonym.
SUM expr [datatype] [INTO var]
Evaluates expr on each iteration, and accumulates the sum of all the values. datatype defaults to NUMBER, which for all practical purposes is NOTYPE. Note that specifying datatype implies that both the sum and the number being summed (the value of expr) will be of that type.
MAXIMIZE expr [datatype] [INTO var]
Computes the maximum of expr over all iterations. datatype defaults to NUMBER. Note that if the loop iterates zero times, or if conditionalization prevents the code of this clause from being executed, the result will be meaningless. If LOOP can determine that the arithmetic being performed is not contagious (by virtue of datatype being FIXNUM or FLONUM), then it may choose to code this by doing an arithmetic comparison rather than calling MAX. As with the SUM clause, specifying datatype implies that both the result of the MAX operation and the value being maximized will be of that type. MINIMIZE does the analagous thing using MIN.
Not only may there be multiple accumulations in a LOOP, but a single accumulation may come from multiple places within the same LOOP form. Obviously, the types of the collection must be compatible. COLLECT, NCONC, and APPEND may all be mixed, as may SUM and COUNT, and MAXIMIZE and MINIMIZE.
(loop for x in '(a b c) for y in '((1 2) (3 4) (5 6)) collect x append y) => (A 1 2 B 3 4 C 5 6) (setq list-of-frobs '(1 3.5 4 7)) => (1 3.5 4 7) (loop for x in list-of-frobs count t into count-var sum x into sum-var finally (return (quotient sum-var count-var))) => 3.875
The following clauses may be used to provide additional control over when the iteration gets terminated, possibly causing exit code (due to FINALLY) to be performed and possibly returning a value (e.g., from COLLECT).
WHILE expr
If expr evaluates to NIL, the loop is exited, performing exit code (if any), and returning any accumulated value. The test is placed in the body of the loop where it is written. It may appear between sequential FOR clauses.
UNLESS expr
Identical to “WHILE (NOT expr)”.
The special form LOOP-FINISH may also be called from Lisp code (e.g., in a DO clause) to terminate a LOOP “normally” (the same as implicit termination by an iteration driving clause, or by the use of WHILE or UNTIL). The epilogue code (if any) will be run, and any implicitly collected result will be returned as the value of the LOOP.
ALWAYS expr
Causes the LOOP to return true iff expr always evaluates true. If expr ever evaluates to NIL, the loop immediately returns NIL without running the epilogue code (if any, as specified with a FINALLY clause). Otherwise, T will be returned when the loop finishes, after the epilogue code hs been run.
NEVER expr
Causes the loop to return true iff expr always evaluates to NIL. This is equivalent to “ALWAYS (NOT expr).”
THEREIS expr
If expr evaluates true, then the iteration is terminated and that true value is returned immediately (without running the epilogue code).
These clauses may be used to "conditionalize" the following clause. They may precede any of the side-effecting or value-producing clauses, such as DO, COLLECT, ALWAYS, or RETURN. Multiple clauses may be conditionalized by the same conditional by joining them with AND; conditionals may also nest.
WHEN expr
If expr evaluates to NIL, the following clause will be skipped, otherwise not. IF is a synonym.
UNLESS expr
(loop for i from 7 to 20 when (zerop (remainder i 3)) collect i and do (print i)) 9 12 15 18 => (9 12 15 18) (loop for i from 7 to 20 when (zerop (remainder i 3)) do (print i) and when (zerop (remainder i 2)) collect i) 9 12 15 18 => (12 18)
Web-Only Note:
(loop for x from 1 to 3 if (oddp x) do (print x) else do (print 'foo)) 1 FOO 3 => NIL (loop for x from 1 to 3 unless (oddp x) do (print x) else do (print 'foo)) FOO 2 FOO => NIL
RETURN expr
Immediately returns the value of expression as the value of the loop, without running the epilogue code. This is most useful with some sort of conditionalization, as discussed in the previous section. Unlike most of the other clauses, RETURN is not considered to "generate body code", so it is allowed to occur between iteration clauses, as in
(LOOP FOR ENTRY IN LIST WHEN (NOT (NUMBERP ENTRY)) RETURN (ERROR ...) AS FROB = (TIMES ENTRY 2) ...)
The datatype keywords which can be used for LOOP variables are as follows:
FIXNUM A fixnum. FLONUM A flonum. INTEGER An integer (a fixnum or bignum). NUMBER Any number (fixnum, bignum, or flonum). NOTYPE Unspecified type (i.e., anything else).
LOOP | Style Note | LOOP vs DO |
LOOP forms are intended to look like stylized English rather than Lisp code. There is a notably low density of parentheses, and there are several synonyms for many of the keywords to allow writing of more euphonious and grammatical “English.” Some feel that this notation is verbose, inappropriate or distasteful, or misleading; others find it flexible, convenient, and expressive. Users of DO are frequently as baffled by the complexity of some LOOP forms as users of LOOP are irritated by the verbosity of the equivalent DO forms.
A controversy exists. I have strong opinions on the issue, but others whom I greatly respect have strong opposing opinions. In the interest of fairness, since there is no universally accepted “conservative position,” I do not here offer any style advice about whether to choose DO or LOOP for any particular application or as a general-purpose tool for use day-to-day. I mean only to point out that there is an issue of style here, and that there are good and bad reasons for choosing either.
Web-Only Note:
LOOP has come into greater acceptance in Common Lisp than it had in MACLISP. I think part of that is that LOOP later evolved have better constraints on clause ordering than it first started out to have, but it took a long time to shake people's memory of the bad times. In MACLISP version 2149, I notice that the following examples (which I had previously made a transcript of in my notes, though I don't know for what version) no longer give a problem. Common Lisp also has the stronger constraints.
;; This is permitted and makes reasonable sense ;; in both MACLISP and Common Lisp (LOOP FOR X FROM 3 TO 5 COLLECT X) => (3 4 5) ;; This is was permitted in MACLISP for a while, ;; even though the result was not intuitive. ;; In later versions of MACLISP, an error would result: ;; "Iteration is not allowed to follow body code" ;; Common Lisp also disallows this. (LOOP COLLECT X FOR X FROM 3 TO 5) => (3 4 5 6) ;; This one is even more odd. ;; In latter day MACLISP it would give this error: ;; "Duplicated iteration variable somewhere in LOOP" ;; Common Lisp also disallows this. (LOOP FOR X FROM 3 TO 5 COLLECT X FOR X FROM 3 TO 5) => (3 5) ;; This one works compatibly in both old and new MACLISP, ;; and even in Common Lisp. Strictly speaking, there's ;; nothing wrong with it, but some might wish for an ;; error here just to avoid confusion. (LET ((TO 3) (DO 5)) (LOOP FOR FROM FROM TO TO DO DO (PRINT FROM))) 3 4 5 => NIL
Sequentializers |
PROGN | Special Form | (PROGN form1 form2 ...) |
The forms are evaluated left to right and the value of the last form is returned. Although lambda-expressions, PROG forms, DO forms, and COND forms all use PROGN implicitly, there are occasions upon which one needs to evaluate a number of forms for side-effects when not in these forms. PROGN serves this purpose.
Examples:
(PROGN) => NIL ;trivial case
(PROGN (SETQ A 1) (CONS A '(STUFF))) => (1 STUFF)
Strong Exits |
^G (Uparrow G) | Function | (^G) |
Typing the character Control-G to lisp will stop the execution of any running lisp code and do a full stack unwind, returning to toplevel.
This functionality can be invoked from code by the function whose name is ^G (an uparrow followed by a G, not a Control-G).
This function does what the obsolete form (IOC G) used to do in Old-I/O lisp.
QUIT | Function | (QUIT [flag]) |
With no flag, QUIT causes the lisp subsystem to remove itself and return to its caller. Execution halts and the current Lisp environment may be lost. (On Tops-20, the return to the exec is non-fatal; if continued by the user, QUIT returns NIL.)
The effect of an argument to QUIT is not defined except on ITS. There it should be interpreted by the following table:
none Standard kill, with notification. T Silent kill, no notification. ERROR Error kill, with error notification. Terminal input is reset. fixnum Uses the fixnum as the effective address in a .BREAK 16, instruction.
*** Warning! At the time of this manual, there is a bug wherein QUIT does a .LOGOUT 1, before it looks at the argument, so the argument has no effect since the Lisp goes away before it can do anything with it. This should probably be fixed sometime.
CATCH/THROW |
*CATCH | Special Form | (*CATCH tagspec . body) |
*CATCH is the Maclisp primitive for doing structured non-local exits. Both argument positions are evaluated in the normal order (left to right), but under control of *CATCH rather than the normal function calling strategy in order that the right things can happen.
The first thing done by *CATCH is to evaluate the first argument position, tagspec, which should yield an atomic catch tag or a list of catch tags. Once that is done, *CATCH then evaluates the forms in the body, returning the result of the last form unless a throw is done during that evaluation. If during the execution of the body of a *CATCH, a throw is done (see *THROW) to the tag specified in tagspec (or to one of them if more than one was specified), then the execution of the *CATCH's body terminates immediately, returning the value thrown.
*CATCH expressions can be nested meaningfully. They affect only *THROW to the particular tag(s) named in their tagspec. If more than one *CATCH is looking for a tag which is thrown to, the innermost (most recently instantiated) catch frame will take precedence.
Unlike the behavior of PROG and RETURN, which must be used together in the same lexical contour, *CATCH and *THROW have dynamic scope. This means that it is acceptable (in fact, very common) to put a *CATCH around a call to another function which then throws to that tag.
Note: There are also primitives CATCH and THROW which are archaic and should not be used. They are supported by macro expansion into appropriate *CATCH and *THROW forms for backward compatibility, but at some point, that compatibility will be flushed. It is worth noting, however, that a THROW can throw to a tag in a *CATCH and a *THROW can be caught by a CATCH with the matching tag.
Multics users must (%include other_other) to get *CATCH.
See also: *THROW, CATCH, CATCH-BARRIER, CATCHALL
*THROW | Function | (*THROW tag value) |
Works in conjunction with *CATCH (see *CATCH). If a *THROW is done to a given tag while executing the body of some *CATCH which specifies the same tag, the execution of that *CATCH's body stops immediately and the *CATCH returns the given value.
If more than one *CATCH frame exists for tag, the innermost (most recently instantiated) *CATCH is the one returned from. If no *CATCH frame exists for tag, an error will result.
Multics users must (%include other_other) to get *THROW.
;;; Some simple examples of *CATCH and *THROW (*catch 'foo (list 'a (*catch 'bar (*throw 'bar 'b)))) (A B) (*catch 'foo (list 'a (*catch 'bar (*throw 'foo 'b)))) B (*catch 'foo (list 'a (*catch 'bar 'c))) (A C) (*throw 'not-there nil) ;NOT-THERE NO CATCH SEEN FOR THIS TAG - THROW ;;; More complex examples involving throwing from within a function ;;; call to a *CATCH which isn't lexically apparent. (defun foo (x) (*throw 'foo x)) FOO (*catch 'foo (list 'a (*catch 'foo (list 'b (*catch 'bar (+ (foo 4) 3)))))) (A 4) (*catch 'foo (list 'a (*catch 'bar (list 'b (*catch 'foo (+ (foo 4) 3)))))) (A (B 4)) (*catch 'foo (list 'a (*catch 'foo (foo 3)) (*catch 'bar (+ (foo 4) 5)))) 4
;;; Here we give an example of using *CATCH and *THROW in the ;;; micro-world of a highly(!) simplified parser. ;;; Define control abstractions (defmacro catching-parse-errors (&body forms) `(*catch 'parse-error ,@forms)) (defun parse-error (format-string &rest format-args) (lexpr-funcall #'format t format-string format-args) (*throw 'parse-error nil)) ;;; Define main parser (defvar *input-buffer* nil "Holds parse tokens for PARSE and friends.") (defun parse (*input-buffer*) (catching-parse-errors (list 'S (parse-np) (parse-vp)))) (defun parse-np () (cond ((memq (car *input-buffer*) '(a an the)) `(NP (DET ,(pop *input-buffer*)) (N ,(pop *input-buffer*)))) (t (parse-error "~&Bad word in noun phrase: ~A~%" (car *input-buffer*))))) (defun parse-vp () (cond ((memq (car *input-buffer*) '(walks talks)) `(VP (V ,(pop *input-buffer*)))) (t (parse-error "~&Not a verb: ~A~%" (car *input-buffer*)))))
(parse '(the man walks)) => (S (NP (DET THE) (N MAN)) (VP (V WALKS))) (parse '(the man eats)) Not a verb: EATS => NIL (parse '(he is tall)) Bad word in noun phrase: HE => NIL
CATCH | Special Form | (CATCH form [tag]) |
CATCH is an archaic (destined to become obsolete) special form for doing structured non-local exits. See documentation on its replacement, *CATCH.
old new (CATCH form tag) (*CATCH 'tag form) (CATCH form) (*CATCH NIL form).
Historically, (CATCH form) evolved to handle the fact that programmers were using
(ERRSET (...(ERR)...))
to accomplish non-local returns since there was once no other way to get that functionality. CATCH and THROW were introduced so that programmers could write
(CATCH (...(THROW val)...))
instead where there was really no error condition. However, it was found that confusion would often result using unlabelled CATCH/THROW because an unlablled CATCH could catch a throw it hadn't intended to. This is why named CATCH was invented. It is strongly recommended, therefore, that if you are re-writing (CATCH form) to a *CATCH according to the above rules, you also go to the extra trouble to choose some tag. This is not as easy because it involves changing related THROW's in the same module to all use the same tag (and perhaps other CATCH's, or even some *THROW's and/or *CATCH's), but it'll enhance the reliability of your code quite a lot.
See also: *CATCH, CATCH-BARRIER, CATCHALL, THROW, ERRSET
THROW | Special Form | (THROW form [tag]) |
THROW is an archaic (destined to become obsolete) special form. See documentation on its replacement, *THROW.
old new (THROW form tag) (*THROW 'tag form). (THROW form) (*THROW NIL form).
CATCH-BARRIER | Special Form | (CATCH-BARRIER taglist form1 form2 ...) |
CATCHALL | Special Form | (CATCHALL handler form1 form2 ...) |
[PDP-10 Only] Has the same semantics as *CATCH except that all *THROWs, independent of tag, will be caught. The handler must be a function of two arguments. If a *THROW occurs, the handler will be called on the tag and value passed by the *THROW. The handler may itself issue a *THROW, in which case the CATCHALL acts like a filter between the exiting function and stack frames farther outward.
See also: *CATCH, CATCH, CATCH-BARRIER
UNWIND-PROTECT | Special Form | (UNWIND-PROTECT form . cleanupforms) |
The form is evaluated and, regardless of how it is exited (normal return, RETURN, *THROW, ERROR, ^G, etc. -- no, QUIT is not handled), the cleanupforms will be executed. All cleanupforms are executed with interrupts disabled.
This is a sophisticated primitive which can be used to implement arbitrary kinds of state-binding without fear that odd kinds of returns (error returns, ^G quits, etc.) will violate the binding.
;; Define a powerful binding primitive (defmacro bind ((form value) &body body) ;value (let ((old-value-var (gensym))) `(let ((,old-value-var ,form)) (unwind-protect (progn (setf ,form ,value) ,@body) (setf ,form ,old-value-var))))) => BIND ;; Do variable setup (setq x '(a b c)) => (A B C) ;; Try our macro (bind ((car x) 'new) (print x) x) (NEW B C) => (A B C) ;; Make sure X has been set back. x => (A B C)
See also: *THROW, *CATCH, CATCH-BARRIER, CATCHALL, ERRSET
PROG1 | Special Form | (PROG1 form1 form2 ...) |
Evaluates form1, form2, ... from left to right, but remembers the value returned by form1 and returns that as the value of the PROG1 form.
(SETQ X 3 Y 4) => 4 (LIST X Y) => (3 4) (SETQ X (PROG1 Y (SETQ Y X))) ;Exchange value of X and Y => 4 (LIST X Y) => (4 3)
Synonym:
(PROG2 NIL form1 form2 ...)
PROG2 | Special Form | (PROG2 form1 form2 form3 ...) |
Evaluates its arguments from left to right, like any LSUBR, and returns the value of its second argument. Like PROG1 but returns the value of form2. This allows forms to be evaluated before and after the value which is actually returned by the PROG2.
Note: PROG2 existed in Maclisp far earlier than PROG1. As such, idioms like (PROG2 NIL ...stuff...) or (PROG2 0 ...stuff...) may still appear frequently in code. These would presumably have been written as (PROG1 ...stuff...) had PROG1 been available when the code was written.
(PROG2 (PRINT '(ADDING 3 AND 4)) ;to do beforehand (+ 3 4) ;to be returned (PRINT 'DONE)) ;to do afterward (ADDING 3 AND 4) DONE => 7
Synonym:
(PROG1 (PROGN form1 form2) form3 ...)
PROG2 | Style Note | (PROG2 x y) |
Some novice programmers perceive a choice between using (PROGN x y) and (PROG2 x y). In such cases, the PROGN form should always be preferred. When reading code, a use of PROG2 should alert the reader that something tricky is likely to be going on with return values; in the two argument case, nothing tricky is going on, so using PROG2 might cause a `false alarm.'
Binding |
PROGV | Special Form | (PROGV *bvl *vals form1 form2 ...) |
Is like a PROGN in that it evaluates form1, form2, etc from left to right returning the last form, however its important feature is that it performs binding. *bvl is evaluated and should evaluate to a list of symbols. *vals evaluates and should evaluate to a list of forms to bind each of the respective symbols from *bvl's result to. The bindings are then performed and the forms are evaluated with those bindings in effect.
This is the only Lisp operator which will allow a user to determine at runtime what variables he will bind (barring runtime macroexpansion, which would not allow the body to be compiled or would require it to be compiled as a separate function making all its variables special). PROGV does relatively little typechecking about the length or content of the results of *bvl and *vals, so the user should use some caution in that realm.
Use of PROGV in day-to-day programming is discouraged because it thwarts the compiler's ability to know the scope of the variables referenced in its body. The only intended use of PROGV is for building imbedded interpreters. If you find yourself needing PROGV when not building an imbedded interpreter, you probably want to learn about macros.
|
The Revised Maclisp Manual (Sunday Morning Edition) Published Sunday, December 16, 2007 06:17am EST, and updated Sunday, July 6, 2008. |
|