The Loop Macro Many of the CL “functions” are actually macros (let, progn, if, etc) The most complicated macro in CL is probably the Loop macro –The Loop macro gives you an alternative loop other than do, dotimes, dolist –This Loop can act like a counter-controlled loop, a conditional loop, or some combination –What makes the Loop unique is that it allows you to take the result of each iteration and combine them into a collection or into a single atom –We explore the Loop macro here, not in complete detail because there does not seem to be any single reference that completely understands the Loop instead, we will mostly explore it through examples
What Loop Can Do To start off with, we look at some specific examples just to see how the Loop macro can be varied to do different things Simple counting loop: (loop for i in '(1 2 3) do (print i)) NIL More complex usage: (loop for i in ’(1 2 3) collect (* i 2) do (print i)) (2 4 6) Using sum: (loop for i in '(1 2 3) sum i) 6 Combining: (loop for x in '(1 2 3) for y in '(4 5 5) collect (list x y)) ((1 4) (2 5) (3 5)) Nesting: (loop for i in '(1 2 3) collect (list i (loop for j in '(4 5 6) collect j))) ((1 (4 5 6)) (2 (4 5 6)) (3 (4 5 6)))
Components of the Loop Loop prolog –Components evaluated prior to loop execution including variable initializations and any initially clause Loop body –The forms that are executed during each iteration, they include executable statements in the block of code, termination tests, step increments, and collection statements Loop termination –Statements in a finally clause are executed here and whatever was accumulated is returned (otherwise the loop returns nil)
Example Loop Components (loop for i from 1 to (compute-top-value) ; first clause while (not (unacceptable i)) ; second clause collect (square i) ; third clause do (format t "Working on ~D now" i) ; fourth clause when (evenp i) ; fifth clause do (format t "~D is a non-odd number" i) finally (format t "About to exit!")) ; sixth clause This loop contains –An initialization clause i from 1 to what (compute-top-value) returns –Two terminating clauses (one explicit) either once i exceeds the top value, or if i is unacceptable –A collection clause which will return the list of each (square i) –A body the do statement which outputs one to two statements depending on if the when clause is t or nil –A finalize clause which exits once the loop is ready to terminate
Loop Initializations Loops will always start with (loop… Followed by any initializations which are done using for and as –for is used to initialize variables sequentially and as is used to initialize variables in parallel Loop stepping and termination are determined by supplying several possible terms: –in list – iterate for each item in the supplied list –from x to y – iterate across the range supplied alternatives are downfrom, upfrom, downto, upto, below, above step-size defaults to 1/-1 but you can supply “by” to change the step size (such as from 3 to 10 by 2) from and by can be given in either order (for i by 3 from 1 to 100…) –on list – same as in list except that the whole list is used during each iteration and then the list is set to the cdr of the list –x = y then z – initializes x to y and then changes it to z afterward this makes more sense when z is an expression like (+ x 5) –across array – binds the variable to each element of a given array (for i across x do…) – note that this covers the entire array even nil items
Examples Note: all of these examples will just print out the sequence (loop for a in '((1 2 3) (4 5 6) (7 8 9)) do (print a)) (1 2 3) (4 5 6) (7 8 9) (loop for a from 1 to 10 by 2 do (print a)) – prints (loop for a downfrom 10 to 1 do (print a)) – prints … 1 (loop for a from 1 below 10 by 4 do (print a)) – prints (loop for i upfrom 10 to 20 do (print i)) – prints … 20 (loop for i on '( ) do (print i)) ( ) (2 3 4) (3 4) (4) (loop for a = 1 then (+ a 10) for i from 1 to 5 do (print a)) – prints (loop for a across #(1 2 3) do (print a)) – prints 1 2 3
More on Initializations Any variables initialized in the loop are bound inside the loop only and lose their scope afterward –The with statement allows you to declare and initialize additional local variables aside from any declared through the initialization processes we saw on the last slide the with statements perform initializations sequentially (as in let*) use and in between the initializations to perform them in parallel –Note that it is difficult to use with/and when using the various other initializations covered on the last slide, so these are less useful Example: (loop with a = 1 with b = a collect b) –This is an infinite loop since we did not provide a terminating condition Example: (loop for i in ’(1 2 3) and a = i collect a) –returns (nil 1 2) while (loop for i in ’(1 2 3) with a = i collect a) –returns (nil nil nil)
Accumulation Clauses After initialization, your loop can contain executable statements as you would include in do, dotimes and dolist statements But you can also specify accumulation clauses –collect – collect the response of the next statement into a list, which is returned after the loop terminates –append – same as collect except that the items collected are expected to already be in a list –nconc – same as append but destructive –sum – performs a running total on the value returned by the next statement, and returns this total when the loop terminates –count – counts and returns the number of times the next statement is true –maximize, minimize – returns the max/min value provided by the next statement across all loop iterations
Examples (loop for i from 2 to 100 append (if (prime i) (list i) nil)) note the use of append here, if we used collect, we would get nil’s in our return list (2 3 nil 5 nil 7 …) (loop for i from 2 to 100 count (prime i)) – returns 25 (# of primes in ) (loop for name in '(fred sue alice joe june) for kids in '((bob ken) ( ) ( ) (kris sunshine) ( )) collect name append kids) (FRED BOB KEN SUE ALICE JOE KRIS SUNSHINE JUNE) (loop for i in '(bird 3 4 turtle (1. 4) horse cat) when (symbolp i) collect i) (BIRD TURTLE HORSE CAT) (loop for i across a maximize i) Returns the maximum from array a
Finally The finally clause is optional –If specified, it is executed one time after the final loop iteration –The finally clause can used to clean up what the loop was doing (for instance, closing a file) Note that the finally clause is circumvented on certain circumstances that we will cover (loop for i from 1 to 10 by 3 collect i finally (print i)) 13 ( ) (loop for i from 2 to 100 with x = 0 append (if (prime i) (list i) nil) do (if (prime i) (setf x (+ x 1))) finally (print x)) 25 ( ) (loop for i from 1 to 10 with x = 2 sum (if (= (mod i x) 0) (progn (setf x (+ x 1)) i) 0) finally (print x)) 11 54
Repeat Clause Instead of using variable initialization over some range (such as with in, from, to, etc) you can use the repeat clause –This evaluates the next form and iterates that many times (loop repeat 10 …) – repeats 10 times (loop repeat (foo x) …) – repeats (foo x) times The form is only evaluated once The repeat clause does not provide you a loop index to reference but you can make your own –(loop repeat 10 for a = 1 then (+ a 1) do (print a)) Notice here that a is a local variable but not controlling the loop If the form returns 0 or a negative number, the loop body is never executed but the finally clause is executed –Consider the following loop – it will print repeating from 0 to 9 times and then print done (loop repeat (random 10) do (print 'repeating) finally (print 'done)
Prematurely Exiting A Loop A for loop can be prematurely terminated by using one of –always, unless, thereis – which can return a specified value –until – a given condition becomes true –while – a given condition is not false The first group (always, never, thereis) will return a t/nil value from the loop upon termination –these return t if the condition is not violated or nil otherwise always returns t if the condition provided is always t never returns t if the condition provided is never t thereis returns t if the condition provided is t once (the loop terminates as soon as one instance is found) –Note: these cannot be used in conjunction with a collection mechanism without using an into clause – see the example that follows If there is a finally clause –it is executed after a normal loop terminates whether normally or by until or while –but it does not get executed if the loop is terminated by always, never or thereis
More Examples (loop repeat 10 with x = 1 until (> x 10) do (print x) (setf x (+ x x))) -- prints and returns NIL (loop for i from 120 to 130 never (prime i) do (print i)) -- prints the list 120 through 126 and returns nil (loop for i from 100 to 200 do (print i) thereis (prime i)) -- outputs 100 and 101 and then returns t (loop for i from 1 to 10 until (> (* i i) 60) collect (* i i) into x finally (return x)) -- returns ( ) notice “into x” which allows us to collect a value while using a terminating condition (loop for i across a never (null i) sum i into x finally (return (/ x (length a)))) Here, we are summing up all elements of array a into variable x as long as none are nil, the loop terminates if we find a nil and returns nil, otherwise this returns the average
Some More Loop Examples (setf *calories* 0) (defun eat ( ) (declare (special *calories*)) (setf *calories* (+ *calories* (+ 50 (random 250))))) (defun hungry-p ( ) (declare (special *calories*)) (< *calories* 600)) (loop while (hungry-p) do (eat)) NIL *calories* 640 (let ((stack '(a b c d e f))) (loop for item = (length stack) then (pop stack) collect item while stack)) (6 A B C D E F) (loop for i from 1 to 3 do (print i) (print (* i i))) this loop has two executable statements so prints 6 things ( )
Using When and Unless Two additional clauses that can be supplied in the Loop macro are when and unless –Recall the functions (when condition body) and (unless condition body) –In this case, like with other Loop clauses, when/unless will not appear in ( ) –We can also use this with a return clause to prematurely exit the loop (and optionally return a value) (loop for i from 100 to 200 when (prime i) sum i) (loop for i in ’(1 2 a 3 b 4 “hi”) when (numberp i) collect i) (loop for i in '(1 2 a 3 b 4) when (numberp i) unless (evenp i) do (print i)) (loop for i in '(1 2 a 4 5) unless (numberp i) return i sum i)
Some Variations Initialization and stepping can also be done to the values of a hash table or values in a package –We will omit those so that its not more confusing than necessary! If we do not supply any Loop keywords, we have an infinite loop –(loop body) is an infinite loop on body –we can use return statements to exit the loop to prevent it being infinite Loops can be nested Loop iterators can be applied in parallel –(loop for x from 1 to 10 for y from 11 to 20 do (print (list x y)) – prints the list ((1 11) (2 12) (3 13)…(10 20)) Loop clauses can be grouped –see the example on the next slide The variable it is a specially reserved variable: –(loop for i in '( ) when (and (> i 3) i) collect it) returns (4 5 6) –(loop for i in '( ) when (and (> i 3) i) return it) returns 4
Grouping Clauses Example (loop for i in '( ) when (oddp i) do (print i) and collect i into odd-numbers and do (terpri) else ; i is even. collect i into even-numbers finally (return (values odd-numbers even-numbers))) > 1 > > 2345 > > 323 > > 235 and returns ( ), ( )
Follow These Rules The Loop mechanism can be treacherous to use and difficult to understand how and when to use it –However, if you want to try, your loop must be in the proper order Start with your initially clause (if any) Follow it by your for, with and repeat clause(s) Next you can supply your conditional clauses (when/unless) and unconditional clauses (do) and any accumulation statements or termination test(s) End with your finally clause –When the loop is iterating, the body is executed by first stepping through any iteration control variables then doing in the order that they appear in the loop body –any conditional or unconditional execution statements, any accumulation clauses, any termination test clauses if any of the clauses in the loop body terminate the loop, the rest of the body is skipped and the loop returns the finally clause is executed unless the loop is terminated by a return, always, never or thereis clause