180 likes | 274 Views
Understand Loop macro in Common Lisp with examples and components like prolog, body, termination, and initialization. Explore varied usage scenarios and complex loops.
E N D
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 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))) Simple counting loop: (loop for i in '(1 2 3) do (print i)) 1 2 3 NIL More complex usage: (loop for i in ’(1 2 3) collect (* i 2) do (print i)) 1 2 3 (2 4 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 1 3 5 7 9 (loop for a downfrom 10 to 1 do (print a)) – prints 10 9 8 … 1 (loop for a from 1 below 10 by 4 do (print a)) – prints 1 5 9 (loop for i upfrom 10 to 20 do (print i)) – prints 10 11 12 … 20 (loop for i on '(1 2 3 4) do (print i)) (1 2 3 4) (2 3 4) (3 4) (4) (loop for a = 1 then (+ a 10) for i from 1 to 5 do (print a)) – prints 1 11 21 31 41 (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 2..100) (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 (1 4 7 10) (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 (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97) (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 1 2 4 8 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 (1 4 9 16 25 36 49) 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 (loop for i from 1 to 3 do (print i) (print (* i i))) this loop has two executable statements so prints 6 things (1 1 2 4 3 9) (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)
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 '(1 2 3 4 5 6) when (and (> i 3) i) collect it) • returns (4 5 6) • (loop for i in '(1 2 3 4 5 6) when (and (> i 3) i) return it) • returns 4
Grouping Clauses Example (loop for i in '(1 324 2345 323 2 4 235 252) 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 (1 2345 323 235), (324 2 4 252)
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