Title: Control Structures
1Control 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
2Progn 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
3If, 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
4Some Examples
(if (gt age 21) (setf legal t)) are you of
legal age? (setf legal (if (gt age 21) t nil))
why is this a better way to do this? (if (gt a
b) a b) return whichever is
larger (if (gt a b) (if (gt a c) a c) (if (gt 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 (gt x 0)
(progn (setf y (sqrt x)) (print y)) (progn
(setf y 0) (print no squareroot possible)))
5When 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 whens 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))
6Cond Statement
- Cond is lisps 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!
7Examples
(cond ((gt grade 90) (setf letter 'a)) ((gt
grade 80) (setf letter 'b)) ((gt grade 70)
(setf letter 'c)) ((gt grade 60) (setf letter
'd)) (t (setf letter 'f))) (setf letter
(cond ((gt grade 90) 'a) ((gt grade 80)
'b) ((gt grade 70) 'c) ((gt 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)))
8More on Cond
- 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 statements result returns
(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 ((gt y 0) 1) (( y 0) 0)
(t -1)))
9Dotimes
- Lets 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
10Dolist
- 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 (well
discuss this shortly)
(dolist (a (1 2 (3 4) 5 6 (7 (8 9) 10)) ) only
iterates 6 times
11The 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 dont 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) )
12Some 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))) ((gt j n)) (print j)) iterates
from 1 to n instead of 0 to n-1 (do ((j 1 ( j
1))) ((gt j n)) nested do loops to print a
multiplication (setf temp nil) (do
((k 1 ( k 1))) ((gt 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 couldnt we make prime a
loop variable like j?
13More 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
((lt temp 0) (/ sum i))) iterate while temp
gt 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
14Another 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)) ((gt a 55)) (print a))
15Do 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)) ((gt 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)) ((gt 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!
16And/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
17Prematurely 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 (gt a b) (code here to
do something when a gt b))) thus, if a lt b, the
function does nothing and just returns nil
18Block, 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 statements result will be returned - however, the block statement allows you to
include return or return-from statements to
prematurely exit - Blocks form (block name body)
- where name is any name you want to provide
- Returns form (return value)
- this returns to the next level of the stack,
returning value - Return-froms form (return-from name value)
- name must be the blocks 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
19Examples
(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 (gt a 100) (return-from secondblock))
(setf b ( a 15)) return nil if we exit
from here (print (list a b))))
20Another 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
21Labels
- Labels represent named locations
- labels can be placed anywhere in a function
- A statement can cause execution to resume at a
labels 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
22Go
- CLs 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 (lt x 0) (go exit))
(setf y ( y x))) exit (print y))
23Catch 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 dont 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) )
24Unwinding
- 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
(progn (open-file) (read-file)
(close-file))
(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
25Mapping 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
26Mapcar
- 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
(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)
- 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 dont 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
27Maplist
- 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
28Other 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
29Returning 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 (gt a
max) (setf max a)) (if (lt
a min) (setf min a))) (values min max)))
30More
- 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 wont cover include
- Multiple-value-prog1
- Multiple-value-bind
- Multiple-value-setq