Control Structures - PowerPoint PPT Presentation

About This Presentation
Title:

Control Structures

Description:

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 ... – PowerPoint PPT presentation

Number of Views:44
Avg rating:3.0/5.0
Slides: 31
Provided by: NKU3
Learn more at: https://www.nku.edu
Category:

less

Transcript and Presenter's Notes

Title: Control Structures


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 (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)))
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 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))

6
Cond 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!

7
Examples
(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)))
8
More 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)))
9
Dotimes
  • 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

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 (well
    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 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) )
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))) ((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?
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
((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
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)) ((gt 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)) ((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!

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 (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
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 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

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 (gt 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
  • 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

22
Go
  • 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))

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 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) )
24
Unwinding
  • 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
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
  • 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

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 (gt a
max) (setf max a)) (if (lt
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 wont cover include
  • Multiple-value-prog1
  • Multiple-value-bind
  • Multiple-value-setq
Write a Comment
User Comments (0)
About PowerShow.com