Download Presentation
## Advanced Functions

- - - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - - -

**Advanced Functions**• In CL, functions are often supplied as parameters to other functions • This gives us tremendous flexibility in writing functions whose specific behavior will be determined later when someone else (or you) supplies a function • functions can be passed as parameters • functions can be specified as arguments • functions can be applied to a given list of data to use the results of that application • Here, we consider how to do these various things • what permits this is that a function in CL is treated like any other data type and is pointed to by a pointer • to denote that something is a function, it is preceded by #’ as in #’evenp or #’> or #’length • the basic REPL function eval, can be applied at any time • similarly, alternative functions exist to apply a function to data (mapping functions, funcall, apply, etc)**Example**• Consider that you want to write a plotting function that, given values x and y, will plot the values f(i) for i from x to y • This is easy to write if you already know f: • The problem with this is that plot only works for function f (defun plot (x y) (let (temp) ;; used to store f(i) (do ((i x (+ i 1))) ;; iterate for i from x to y ((> i y)) (setf temp (f i)) ;; temp = f(i) (dotimes (a temp) ;; do f(i) times (format t “*”)) ;; print an * (format t “~%”)))) ;; start a new line afterward**Solution – Pass the Function**• Here, we pass the function that we want to apply as a parameter • To apply the function to a value, i in this case, we use funcall We call plotf passing it #’functionname and x and y as in (plotf #’add_1 5 10) if we have a function called add_1, or (plotf #’square 5 10) if we have a function called square (defun plotf (f x y) (let (temp) (do ((i x (+ i 1))) ((> i y)) (setf temp (funcall f i)) (dotimes (a temp) (format t "*")) (format t "~%"))))**Another Approach: Apply**• Aside from using funcall, which expects as params a function and parameter(s), you can use apply • Apply applies a function to a list of parameters where the function can be any of • a compiled function • (apply #’max ’(3 5 4 2)) 5 • a lambda-expression • (apply #’(lambda (x y) (if (> x 0) y (* y -1))) ’(0 3)) -3 • a symbol (which could be stored in a variable or provided in the apply statement) • (setf f ’+) • (apply f ’(1 2)) 3 • Note: since apply expects the parameter(s) to be in a list, if the function to be applied is one that expects a single atom as a parameter, it still must be placed into a list • (apply #’numberp (list x)) is correct • (apply #’numberp x) is incorrect**Lambda Functions**• A lambda function is a nameless function • Having a function with no name seems useless • how can you use it? • There are occasions where you will need a function one time to be applied right away • you can define a lambda function for this purpose • There are numerous functions that apply functions • mapcar for instance • Lambda functions are written using the following notation: • #’(lambda (params) body) • As in #’(lambda (x) (+ x 1)) this function will return 1 greater than whatever the argument is • you will use the defined lambda function inside of another function, such as in apply or mapcar, and there are other circumstances as we will see later**Mapping Revisited**• Recall the mapping functions • They apply a function to a list and return a new list • mapcar – apply the car of the list to the function and then cons the result to (mapcar function (cdr lis)) • We specify the function as #’name or #’(lambda expression) • (mapcar #’numberp lis) (list of items where T corresponds to those that are numbers and nil to those that are not) • Here is an example of using mapcar with a lambda expression • (mapcar #’(lambda (x) (if (>= x 0) x (* x -1))) lis) this returns the list of (hopefully numbers) after applying absolute value • alternatively, we can achieve the same by doing • (mapcar #’abs lis) • Now that we’ve seen the basics of applying functions, we reconsider some of the functions covered earlier in the semester and see that they are far more flexible than we originally thought**Functions that Apply Functions**• Many of the sequence functions can take functions as additional parameters • Recall count • (count a b) – counts the number of times a occurs in b • (count 5 ’(3 5 7 8 4 6 2)) 1 • count also has a parameter called :test which can be supplied with a function, if true when applied to the parameter, then count “counts” that parameter • (count a b :test #’>) – counts the number of times that a is greater than each element in b • (count 5 ’(3 5 7 8 4 6 2) :test #’>) 3 • (count 5 ’(3 5 7 8 4 6 2) :test #’>=) 4 • Similarly, remove, position, find, delete (the destructive version of remove), replace and subst can all take :test arguments**-if and -if-not Functions**• The same functions have variants that end with -if and -if-not • (count-if #’function lis) • Count the number of elements of lis that, when applied to the function, are t • (count-if #’numberp ’(3 3.3 a #\a t)) 2 • (count-if-not #’evenp ’(6 3 7 2 9 1)) 4 • 4 of the items are not t when #’evenp is applied • Functions that have -if and -if-not variants: • member, find, replace, position, delete, subst, assoc, rassoc • also stable-sort and merge • Example: lis is a list of various atoms (numbers, symbols, characters, t, nil) and we want to return the list that constitutes only numbers: • (remove-if-not #’numberp lis) ;; non-destructive, returns new list • (delete-if-not #’numberp lis) ;; destructive version, lis is altered • Note in this latter version, we would no longer want lis because it still may contain non-numbers • Notice we do not supply an argument to be compared, only a test • (count 5 lis) counts number of 5’s in lis but (count-if #’equal-to-five lis) uses a function to compare the elements of lis**Examples**(remove-if #’numberp ’(a b 3 c 3.3 d 1/3 “e” “11” nil 10)) (a b c d “e” “11” nil) (remove-if-not #’numberp ’(a b 3 c 3.3 d 1/3 “e” “11” nil 10)) (3 3.3 1/3 10) Notice that we cannot use (remove lis :test #’numberp) here this lacks a parameter – the element to be removed (remove-if #’(lambda (x) (not (numberp x))) ’(a b 3 c 3.3 d 1/3 “e” “11” nil 10)) (3 3.3 1/3 10) (count-if #’(lambda (x) (equal x 5)) ’(a 5 b 5 c 3 d 55)) (position-if #’zerop ’(1 2 4 12 0 16 17)) 4 (member-if #’listp ’(a b c d)) nil (member-if #’listp ’(a (b c) (d e f) g (h))) ((b c) (d e f) g (h)) The functions supplied in these examples are predicate functions that use a list element as a parameter, we don’t have the ability to do something like (count-if #’> ’(…)) because we can’t specify the value we want to test > against – yet**:test, :test-not, :count and :key**• In addition to adding –if or –if-not, many of these sequence functions can take :test and :test-not parameters • The sequence function is applied to the element of the sequence if the test result is non-nil (for :test) or nil (for :test-not) • (count 3 ’(3 6 2 1 7 4) :test #’>) 3 (number of elements > 3) • The :count parameter is used to limit the number of elements in a sequence that are examined by the function being applied • this can also be used with :from-end t to work backwards • (remove 3 ’(1 2 3 4 3 5 3 6 3) :count 2) (1 2 4 5 3 6 3) • (remove 3 ’(1 2 3 4 3 5 3 6 3) :count 2 :from-end t) (1 2 3 4 3 5 6) • The :key parameter allows you to supply another function to apply to the parameter prior to applying the main function • For instance, if your list is really a list of lists, you can supply :key car so that your test applies only to the car of each sublist • If lis is ’((csc 375) (cit 140) (eng 200) (csc 402) (mat 385)) then we can count the number of csc coursers as • (count ’csc lis :key #’car)**Additional Examples**• Let lis1 be ’(3 6 2 1 7 4) • And lis2 be ’((a 3) (b 4) (5 c) (6 d) (7 2) (e 8) (3 f)) • (member 3 lis :test #’>) (2 1 7 4) • returns the member of the first item where 3 > item (that is, the first item smaller than 3) • (remove 3 ’(3 6 2 1 7 4) :test #’<) (3 2 1) • returns the list where every number in which 3 < is true is removed (so 6, 7 and 4 are removed since 3 < them) • (member-if-not #’numberp lis2 :key #’cadr) ((5 C) (6 D) (7 2) (E 8) (3 F)) • (remove 3 lis2 :key #’car) ((A 3) (B 4) (5 C) (6 D) (7 2) (E 8)) • (remove-if-not #’oddp lis1) (3 1 7) • What if I want to remove two of the top-level elements from the rear of lis2 where the second element > 4? • (remove 4 lis2 :test #’> :from-end t :key #’cadr) this yields an error • can’t apply > to the letter symbols (such as (6 d)) so instead, lets try this: • (remove-if #’(lambda (x) (and (numberp x) (> x 4))) lis2 :count 2 :from-end t :key #’cadr) ((A 3) (B 4) (5 C) (6 D) (7 2) (3 F))**Sorting**• CL has a built-in sort function which must be supplied the sequence to sort and a function to apply (the test used in sorting such as >) • Form: (sort sequence function) • (sort ’(5 3 6 2 9 8 1 7) #’>) (9 8 7 6 5 3 2 1) • What if I have a list of lists like lis2 previously where each sublist contains an atom and a number in that order? • (sort ’((c 5) (b 3) (d 4) (a 6) (e 1) (f 2)) #’> :key #’cadr) ((A 6) (C 5) (D 4) (B 3) (F 2) (E 1)) • Note: sort is destructive, so that (sort lis1 #’<) from the previous slide will return (1 2 3 4 6 7) and lis1 will now be (1 2 3 4 6 7) • Stable-sort is a variation of sort that is guaranteed to not reorder elements that are equal (this could happen for instance if we had a list like ((c 5) (d 4) (a 5) (b 2) (e 4)) depending on how sort is implemented**Sequence Predicates**• Just as sequences have these functions that take a function as an argument, there are also sequence predicate functions • Each of these takes a function and a sequence and applies the function to each list element until either the predicate has been determined (short-circuiting causes it to stop at this point) or it has reached the end of the sequence • The sequence predicates are • every – all elements of the sequence must pass the test to return t, else nil • some – at least 1 element must pass to return t, else nil • notany – all elements must fail the test to return t, else nil • notevery – at least 1 element must fail to return t, else nil • (every #’plusp lis) • returns t only if every element of lis is a positive number, nil otherwise (or error if not element element of lis can be applied to #’plusp) • (if (notany #’minusp lis) (mapcar #’sqrt lis) (format t “error in taking square root”)) • if notany returns t then mapcar proceeds, otherwise we send an output message**Applying Functions To Multiple Items**• The predicate functions, mapcar, and others can apply to multiple sequences when supplied with a function that takes multiple arguments • Consider that a is the list ’(1 2 3) and b is the list ’(2 4 6) • (every #’> b a) compares 2 to 1, 4 to 2, 6 to 3 and returns t • (some #’= a b) nil since no pair is equal (1, 2; 2, 4; 3, 6) • (mapcar #’+ a b) (3 6 9) • (mapcar #’+ (remove 'evenp a) (remove 4 b)) (3 8) • Notice here that we run the risk of having lists of two different lengths which will work, but may not be what we expect • We also run the risk of a and/or b containing non-numbers • (if (and (every #’numberp (append a b)) (= (length a) (length b))) (mapcar #’+ a b)) • This fixes both of the previous problems**Map and Map-Into**• These are variations of the mapping functions • Map is just like mapcar but takes an additional parameter of the type of sequence to create • (map ’list #’+ #(1 2 3 4) #(2 4 6 8)) (3 6 9 12) • Notice how map, like other sequence operations, can take data of one form (a vector here) and translate it into another (a list) • (map ’string #char-upper ’(#\a #\b #\! #\3 #\c)) “AB!3C” • Map-into is destructive in that it performs the mapping, but rather than returning a new sequence, it places the result into a given sequence • (map-into a #’+ b c) is the same as the vector operation a = b + c assuming that a, b and c are all vectors storing numbers • Assume a may store some non-numbers and we want a pair-wise addition of a and b, here we remove all non-numbers from a and reduce the size of b to be equally lengthed • (map ’vector #’+ (remove-if-not #’numberp c) (remove-if #’numberp d :from-end t :count (- (length c) (count-if-not #’numberp c) 1)) • if c is (1 a b 3 c 4 5) and b is (1 2 3 4 5 6 7), then this returns the vector #(2 5 7 9) – 1 + 1, 3 + 2, 4 + 3, 5 + 4 (we removed 3 letters from c so we removed the last 3 numbers from b)**Reduce**• Form: (reduce function list) atom • This applies the function to the first x arguments in list depending on what the function does (typically 2 arguments) and then applies the result to the next argument, etc through the remainder of the list • It returns the single item that the function is supposed to return, a number for an arithmetic operator, t/nil for a boolean function, etc • Examples: • (reduce #’+ ’(1 2 3 4 5)) 15 • (reduce #’* ’(1 2 3 4 5)) 120 • (reduce #’equal ’(2 2 t t t) t • (reduce #’max ’(3 5 4 2 1) 5 • And of course, we can combine functions: • (reduce #’* (remove 0 lis)) – eliminate 0s first • (reduce #’+ (remove-if-not #’numberp lis)) – eliminate non numbers first • (reduce #’max (remove 100 lis :test #’>)) • eliminate any numbers > 100 first**Other Function Functions**(defun foo (x y) (flet ((square (x) (* x x)) (cube (x) (* x x x))) (if (> (cube x) (square y)) (cube y) (square x)))) (defun foo2 (x y) (flet ((+ (x y) (do ((i 0 (+ i 1))) ((= i (length x))) (setf (nth i x) (+ (nth i x) (nth i y)))) x)) (+ x y))) • flet allows you to define/declare a function locally inside a function • labels is the same as flet except that the functions can be recursive • macrolet is the same as flet but for macros (which we cover next)