1 / 30

Control Structures

Control Structures. We have already visited some of the control structures (if, if-else, dolist, dotimes) Here we take a more in-depth look at control structures aside from what we normally think of as control constructs (loops, selection statements), we will also examine how to

dschaff
Download Presentation

Control Structures

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Control Structures • We have already visited some of the control structures (if, if-else, dolist, dotimes) • Here we take a more in-depth look at control structures • aside from what we normally think of as control constructs (loops, selection statements), we will also examine how to • short-circuit expressions and functions • go to statements (yikes) • We start with a basic idea of control • test a condition, based on the result do the operation or not • for a loop, the operation is the loop body, and then recheck the condition • keep in mind that in CL, conditions do not have to evaluate to T or nil, instead any non-nil value is considered T • for instance (if (member a b) (print (list a ’is ’in b))) • recall member returns the portion of the list b that starts with a, so this prints that a is in the list • but first…

  2. Progn and Variants • We need a block construct • For instance, for the body of an if statement, or the body of a function • Progn creates such a block • Progn returns the value of the last statement • there are also prog1 and prog2 statements, which are the same as progn except that they return the result of the first/second statement • There are also prog and prog* but these differ • These statements allow for local variables to be declared (that is, they combine progn and let) • they also permit the use of tags, which we cover later • prog gives the programmer the ability to write block-structured code to reflect the format of an imperative language • prog* is the same as prog except variables declared for this block are declared using the same approach as let* (whereas prog uses let) • Because the object of CL is to get away from such imperative styles of programming, we will ignore prog and prog*

  3. If, If-Else Reconsidered • The if statement is very simple • (if condition body) • if the condition evaluates to non-nil, execute the body • body is expected to be a single statement • if you want more statements executed, enclose them in a block structure (progn or possibly let) • The if-else statement has two bodies • (if condition body1 body2) • if the condition is non-nil, execute body1, else if the condition is nil, execute body2 • again, body1 and body2 are expected to be single statements, enclose them in let or progn if there are multiple function calls

  4. Some Examples (if (>= age 21) (setf legal t)) ;; are you of legal age? (setf legal (if (>= age 21) t nil)) ;; why is this a better way to do this? (if (> a b) a b) ;; return whichever is larger (if (> a b) (if (> a c) a c) (if (> b c) b c)) ;; return the largest of a, b and c (dotimes (j (- n 1)) ;; output primes and non-primes (if (prime (+ j 2)) ;; dotimes starts at 0, add 2 to j (print (list ((+ j 2) ’is ’a ’prime ’number))) (print (list ((+ j 2) ’is ’not ’a ’prime ’number))))) (if (>= x 0) (progn (setf y (sqrt x)) (print y)) (progn (setf y 0) (print ’no ’squareroot ’possible)))

  5. When and Unless • If and if-else expect single instruction for bodies • So you have to use progn if you have multiple operations to make up the if or else clauses • To get around this, we have when and unless • When: if condition is true, then perform all operations in the when’s body • Unless: if condition is false, then perform all operations in the unless’ body • (when condition body) • (unless condition body) • the body can consist of any number operations without the need of a progn (or let) • but both of these are 1-way selection statements only • In essence • (when condition body)  (if condition (progn body)) • (unless condition body)  (if (not condition) (progn body))

  6. Cond Statement • Cond is lisp’s general purpose selection statement • it predates if/if-else in lisp • the if and if-else were added for convenience • the form is • (cond (condition1 result1) (condition2 result2) … (conditionn resultn)) • each condition is any CL operation that evaluates to non-nil or nil • result can be any group of statements (without let or progn) • the cond statement works as follows: • test each condition one at a time until a condition evaluates to non-nil, then execute the associated result statements and exit the cond • this is like Java/C switch statement or Pascal case statement but: • the test can be any conditional, it is not limited to testing a single variable against one or a list of values • there is no need for break statements as there is with the switch statement • a default statement can be supplied in the form (t result) • t is always true!

  7. Examples (cond ((>= grade 90) (setf letter 'a)) ((>= grade 80) (setf letter 'b)) ((>= grade 70) (setf letter 'c)) ((>= grade 60) (setf letter 'd)) (t (setf letter 'f))) (setf letter (cond ((>= grade 90) 'a) ((>= grade 80) 'b) ((>= grade 70) 'c) ((>= grade 60) 'd) (t 'f))) What does this code do? (cond ((and (evenp (length lis)) (numberp (car lis)) (numberp (second lis))) (setf newlis (list (car lis) (second lis))) (setf lis (cddr lis))) ((numberp (car lis)) (setf newlis (list (car lis))) (setf lis (cdr lis))) (t (setf lis (cdr lis)) (setf newlis nil)))

  8. We can replace the cond with nested if-else statements, but this could become hard to read: You can nest cond statements so that the action portion contains a cond Cond statements can also be used inside of other statements since the cond statement returns whatever the matching statement’s result returns More on Cond (cond ((null lis) nil) ((atom lis) (list lis)) ((atom (car lis)) (list (car lis))) (t (car lis))) (if (null lis) nil (if (atom lis) (list lis) (if (atom (car lis)) (list (car lis)) (car lis)))) • (setf x (cond ((> y 0) 1) • ((= y 0) 0) • (t -1)))

  9. Dotimes • Let’s look at this counter-controlled loop in detail • basic form: (dotimes (index value) body) • iterate from index = 0 up to but not including value,step size is 1 • execute the loop body each time • the index variable is scoped only inside of the loop body and becomes unbound after the loop terminates • note: if this loop variable shares the same name as a variable declared in a let or a parameter, the loop variable overshadows the previously defined instance until the loop terminates • note: if the code in body alters value, it has no effect on the number of loop iterations and value will return to the next value in the sequence during the next iteration • alternate forms: (dotimes (index value result) body) • result is the value that dotimes will return, if not specified, dotimes returns nil

  10. Dolist • Two forms like Dotimes: • (dolist (index list) body) • (dolist (index list result) body) • if there is no result, the dolist returns nil, otherwise it returns result • And like dotimes, the number of iterations is determined when list is first evaluated • if list changes in the body, it does not impact the number of iterations even though list has been altered • dolist operates once for each top-level list item, so if a given list item is a sublist, it is counted only once • In both dotimes and dolist, we can prematurely exit the loop using the return statement (we’ll discuss this shortly) (dolist (a ’(1 2 (3 4) 5 6 (7 (8 9) 10)) …) only iterates 6 times

  11. The Do Loop • Both dotimes and dolist give you easy-to-use loops, but neither is very flexible • If you prefer a flexible loop, use do • form: (do ((var1 init1 step1) (var2 init2 step2) … (varn initn stepn)) (end-test . result) body) • the form is a little misleading in that the end-test is usually going to be a function call (in another layer of parens), you will probably only use one loop variable, and you don’t need the result, so more often it will look like this: • (do ((var1 init1 step1)) ((= var1 final)) body) • as with dolist/dotimes, any variable declared in the do loop has a scope solely within the loop and is unbound afterward • the loop iterates until the condition becomes true, so the above example terminates once var1 equals final, the loop body is not executed during that final time, so these are equivalent: (do ((i 0 (+ i 1))) ((= i n)) …) (dotimes (i n) …)

  12. Some Examples (do ((j 0 (+ j 1))) ((= j n)) (print j)) ;; simple counting loop (do ((j (car lis) (car lis))) ((null j)) (setf lis (cdr lis)) (print j)) ;; same as (dolist (j lis) (print j)) (do ((j 1 (+ j 1))) ((> j n)) (print j)) ;; iterates from 1 to n instead of 0 to n-1 (do ((j 1 (+ j 1))) ((> j n)) ;; nested do loops to print a multiplication (setf temp nil) (do ((k 1 (+ k 1))) ((> k n)) (setf temp (append temp (list (* k j))))) (print temp)) (setf prime t) (do ((j 2 (+ j 1))) ((or (= j n) (not prime))) ;; determine if n is prime or not (if (= (mod n j) 0) (setf prime nil))) ;; why couldn’t we make prime a ;; loop variable like j?

  13. More Examples Compute an average of a list of values input: (do ((i 0 (+ 1 i)) ;; i will count number of iterations (temp (read) (read)) ;; temp will store an input value (sum 0 (+ sum temp))) ;; sum will sum up each input ((< temp 0) (/ sum i))) ;; iterate while temp >= 0, return (/ sum i) Find the location of some value target in a list lis, returning the location: (do ((i 0 (+ 1 i)) ;; counts the iteration, used as return value (temp lis (cdr temp))) ;; iterate through lis ((or (null temp) (equal target (car temp))) ;; iterate until temp is nil or i)) ;; we have found target, then return I Notice both of these examples have no loop body itself

  14. Another Example: Fibonacci • Lets write code to compute the first 10 fibonacci values • Using dotimes versus do • (dotimes (i 8) (setf c a) (setf a (+ a b)) (setf b c) (print a)) • assume initializations of a = 1, b = 1, c = 0 • this will print out the 3rd – 10th values (skipping the first two 1s) • We can do better than this using the do loop: • (do ((i 0 (+ i 1)) (a 1 (+ a b)) (b 0 a)) ((= i 10)) (print a)) • a and b are part of the loop making our loop body a single instruction • also, this loop will print all 10 values, not just 3-10 • In fact, we can do away with i if we know the last fibonacci value that we want to reach: • (do ((a 1 (+ a b)) (b 0 a)) ((> a 55)) (print a))

  15. Do* Loop • A variation of the Do loop is the Do* loop • The difference between these is the same as the difference between let and let*, if a loop variable refers to another loop variable, they are not evaluated in parallel, but instead sequentially • so (do* ((a 1 (+ a b)) (b 0 a)) ((> a 55)) (print a)) no longer has the correct behavior because b takes on the value of a after a is incremented during each loop iteration • we must use a third variable if we want to use do* • (do* ((c a a) (a 1 (+ a b)) (b 0 c)) ((> a 55)) (print a)) • this is more like the typical Fibonacci solution where c takes on the role of a temp variable • do is preferred to do* but should not be used if in fact you need the variables to be evaluated in the sequence that you list them!

  16. And/Or • What do these have to do with control statements? • Nothing exactly, but because of their short-circuited nature, they can be used to control how many functions or statements get evaluated • And – returns the value of the last item if all items evaluate to non-nil, or nil if any items evaluate to nil, in which case all remaining items are left unevaluated • Or – returns the first non-nil value and all remaining items are left unevaluated, or returns nil if all items evaluate to nil • So notice that (and (numberp x) (numberp y) (+ x y)) returns x + y rather than T • (setf a (and (numberp x) (numberp y) (+ x y))) assigns a to be either x + y or nil if x and y are not numbers

  17. Prematurely Exiting • In a typical CL function, you only return from the function after the function terminates • You can use an if statement to wire a function together so that execution stops when you want as follows: • However, you can also use a return statement, similar to in C or Java when you want to exit the function (or block) prematurely • to accomplish this, you can use return or return-from • these can be used inside of do, dolist, dotimes, and block statements as well as the loop statement (covered later) (defun foo (a b) (if (> a b) (code here to do something when a > b))) thus, if a <= b, the function does nothing and just returns nil

  18. Block, Return, Return-From Statements • The block statement is much like progn (or any other of the prog statements) • you can declare a group of statements where the last statement’s result will be returned • however, the block statement allows you to include return or return-from statements to prematurely exit • Block’s form: (block name body) • where name is any name you want to provide • Return’s form: (return value) • this returns to the next level of the stack, returning value • Return-from’s form: (return-from name value) • name must be the block’s name • Note: if value is not supplied, nil is returned automatically • return is identical to return-from except that return returns from a block named nil – which is the case if you are returning from do, dolist, dotimes

  19. Examples (defun reciprocal (a) (block compute ;; prematurely exit if a = 0 (if (= a 0) (return-from compute 0) (/ 1 a)))) (dolist (a lis) (if (not (atom a)) (return a)) ;; return used here since dolist is (print a)) ;; in a block with the default name nil (defun multipleblocks (a b) (block firstblock (if (= a b) (return-from firstblock (list a b))) (setf a (* a b)) ;; if we return from firstblock, we (print (list a b))) ;; still execute secondblock (block secondblock (if (> a 100) (return-from secondblock)) (setf b (+ a 15)) ;; return nil if we exit from here (print (list a b))))

  20. Another Example (defun palindrome-list (lis) (dotimes (i (floor (/ (length lis) 2))) (if (not (equal (car lis) (car (last lis)))) (return-from palindrome-list nil)) (setf lis (cdr lis)) (setf lis (butlast lis))) t) Here, the code iterates for half the list (or half – 1 if the list has an odd number of elements) In each iteration, if the first item and last item are not equal, return from this function returning nil Otherwise, continue in the function, which removes the first and last elements from the list If we make it all the way through to the end of the function, return t

  21. Labels represent named locations labels can be placed anywhere in a function A statement can cause execution to resume at a label’s location as long as the function containing label is active on the run-time stack Branches can occur using several different CL statements: go (go to statement) throw (used in a catch-throw setting) return and return-from Another statement is called tagbody tagbody is a statement that combines several others: prog so that local variables can be bound in the block return statements are allowed in the body go statements are allowed in the body A couple of examples follow but you should avoid using tagbody, prog and go, these imperative features make a functional program (or even a procedural program) very difficult to read and understand Labels

  22. Go • CL’s goto statement has the form: • (go tag) • tag must be a label placed somewhere in the code (prior to or after the go) • the tag itself can be a number or identifier • you can only branch to code within the local block, denoted by prog, prog*, or tagbody • One example of using the go statement is for the loop instruction • (loop body) – this is an infinite loop, to get out of it, use a go within the loop • You should never use go statements! Just ignore this (you never saw this!!!!!) (prog (x (y 0)) (loop (setf x (read)) (if (<= x 0) (go exit)) (setf y (+ y x))) exit (print y))

  23. Catch and Throw • The final form of branch can cross blocks, for instance to terminate functions • the statements are somewhat similar to exception handling as done in C++, you use a throw statement to leave the current block, and control transfers to the catch statement that is closest to it on the run-time stack • While this is similar to exception handling as you may have used it in Java or C++, we will see that CL has its own form of exception handling and that we don’t need to use catch and throw statements (but we could) (defun foo (a b) (defun bar1 (x) (catch ’here (if (= x 0) throw ’here) (bar1 a) …) (bar2 (bar1 b)) (defun bar2 (y) (if (listp y) throw ’here) …)

  24. Unwinding (progn (open-file) (read-file) (close-file)) • Using throws can be dangerous because it aborts the typical flow of processing • Consider: • Imagine that opening or reading the file causes a circumstance where control is thrown to a catch outside of this progn • in which case, the close-file routine never executes, the file is left open • To avoid such a circumstance, we can use unwind-protect • This instruction ensures that all remaining operations are executed prior to the throw taking place – it protects the run-time stack from being unwound beyond a “safe” point • We rewrite the above code as follows: (unwind-protect (progn (open-file) (read-file)) (close-file)) Here, the throw from open-file or read-file is postponed until close-file can execute, and then the throw occurs

  25. Mapping Functions • Another type of function is known as “apply-to-all” • This means that the function receives a group of parameters and it is applied to each parameter, returning a group • In Lisp, this is done by passing lists and returning lists • we could write our own apply-to-all functions easily enough, iterate through the list using dolist, take each item and apply it to our function, appending the result to a temporary list, and then returning the list • but there are several built-in functions to accomplish this action, these are known as mapping functions • The most common mapping function is mapcar • Variants include map, mapcan, mapcon, maphash, map1 and maplist, we take a look at some of these but concentrate on mapcar (defun mapcar (fun lis) (cond ((null lis) nil) (t (cons (fun (car lis)) (mapcar fun (cdr lis)))))) An implementation for mapcar

  26. Mapcar receives two parameters, a function and a list it steps through the list, applying the car of the list to the function, placing the result in a return list, and continuing to iterate on the cdr of the list, returning the return list when done examples: Notice how the last example returns a list containing T or nil, this may or may not be useful for us We can remove elements that we don’t want (such as the nil values) by further calling other mapping functions like remove-if we cover functions like remove-if later in the semester Mapcar (mapcar #’(lambda (x) (* x x)) ’(1 2 3 4 5))  (1 4 9 16 25) (mapcar #’abs ’(-1 2 -3 4 -5 6))  (1 2 3 4 5 6) (mapcar #’prime ’(2 3 4 5 6 7 8 9))  (T T nil T nil T nil nil)

  27. Maplist • Maplist applies the function to an entire list of items, and then reduces the list to be the cdr of the list, and iterates until the list is nil • thus, several elements of the list wind up getting processed numerous times • example: • (maplist #’lambda (x) (if (member (car x) (cdr x)) 0 1) ’(a b c d a c b c b a c e f))  (0 0 0 1 0 0 0 0 1 1 1 1 1) • here, each element of the return list represents whether it is the last time in the list that the item appeared • we could now do something like this: • (dotimes (a (length return-list)) (if (= (nth a return-list) 1) (print (nth a original-list)))) ;; this will print each item one time only • like mapcar, maplist accepts a function and a list and returns a list but here the function expects a list as an argument, not an atom

  28. Other Mapping Functions • Mapcan and mapcon are the same as mapcar and maplist respectively except • when building the return list, they use nconc • the result list then does not have to contain an element for each element in the original list, so mapcan and mapcon act as filters, removing nil elements • (mapcan #’(lambda (x) (and (numberp x) (list x))) ’(1 a b 3 c 4))  (1 3 4) • notice here that if (numberp x) is nil, nothing happens (mapcan does not add this item to the return list) but if (numberp x) is true, then we go on to evaluate (list x), and since x is a number, (list x) returns a list which is then added to the return list • without having (list x), we get an error because nconc operates by taking one list and concatenating it to another list, not individual atoms! • Mapc and mapl (also called map) are like mapcar and maplist except that they do not accumulate the list of values to be returned – and therefore are less useful

  29. Returning Multiple Values • The intention of the mapping functions is to return a list of items based on the list of input • Mathematically, it might be thought of as f(list)  (list’) or map(f, list) = (list f(first(list)) f(second(list)) …) • What if a given function should return two separate items? We could wrap these into a list and return them, but we do not have to • values is a function that permits a function to return multiple values (defun minmax (lis) (let ((min (car lis)) (max (car lis)) (rest (cdr lis))) (dolist (a lis) (if (> a max) (setf max a)) (if (< a min) (setf min a))) (values min max)))

  30. More • Values-list – takes a list and returns the items of the list as individual items • (values-list (list 1 2 3 4))  1 2 3 4, which is the same as (values 1 2 3 4) • Multiple-value-list – returns multiple values (from values or a function that uses values like floor or minmax from the previous slide) in a list • Multiple-value-call – applies a given function to numerous items or results of (values …) calls • (multiple-value-call #’+ (values 1 2 3) (minmax ’(3 2 4 5)))  13 • Other operations that we won’t cover include: • Multiple-value-prog1 • Multiple-value-bind • Multiple-value-setq

More Related