Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Lambda-expression TDDC65 Artificial Intelligence and Lisp Lecture 4 Functions and higher order functions - Functions - lambda-expressions (sec 7.1 Programmering i Lisp) - funcall and function (sec 7.2 Programmering i Lisp) - higher order functions (sec 7.3 Programmering i Lisp) At the definition (defun square (x) (* x x)) the following function is defined: (lambda (x) (* x x)) The name of the function is: square - pattern matching - Lab 2 - Higher order functions and pattern matching An expression (form) in LISP is: (function argument ... argument) How to write Lisp-programs * Common Lisp built in functions * Abstractions * Style (functional / imperative) * Program structure - small functions Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson A function is a first order data object: 3 Σf(i) i=1 (defun sum3 (f) (+ (funcall f 1) (funcall f 2) (funcall f 3))) (sum3 (function sin)) (sum3 #’sin) 3 i=1 Example on a function returning another function as value: (defun choose-function (n) (cond ((= n 1) (function 1+)) ((= n 2) (function (lambda (n) (+ n 2)))) (t (function (lambda (n) (+ n 10)))))) (funcall (choose-function 2) 5) => 7 3 Σi2+3 ((lambda (x y) (* x y)) 10 20) => 200 Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson We can now define a function - taking functions as arguments - returning a function as value Σsin(i) i=1 where function is a lambda-expression or a name . Example: (sum3 (function (lambda (i) (+ (* i i) 3)))) (setq my-fn (choose-function 3)) (funcall my-fn 2) => 12 (my-fn 2) => ? Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Patterns as higher order functions: Define a function which increases every element in a list with 5. (defun increase-5 (l) (cond ((endp l) ’()) (t (cons (+ 5 (first l)) (increase-5 (rest l))) ))) A pattern for traversing a list and applying a function on every element is: (defun fn-mall (l) (cond ((endp l) ’()) (t (cons (”operation” (first l)) (fn-mall (rest l))))))) (increase-5 ’(1 10 50)) => (6 15 55) Define a function creating a new list with all first-elements on its sublists. (defun first-element (l) (cond ((endp l) ’()) (t (cons (first (first l)) (first-element (rest l))) ))) (first-element ’((one ett) (two två) (three tre))) => (one two three) Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson It is now simple to define this pattern. (defun my-mapcar (fn l) (cond ((endp l) ’()) (t (cons (funcall fn (first l)) (my-mapcar fn (rest l))) ))) fn = ”a function taking one argument” Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Map-functions Map-functions in Common Lisp Common Lisp has a large number of higher order functions. Some of them start its name with map. (mapcar #’first ’((a b c) (x y z))) => (a x) (mapcar #'square '(1 2 3 4)) => (12 22 32 42) = (1 4 9 16) Can be used with several list arguments: (mapcar #'(lambda (x) (+ x 10)) '(1 2 3 4)) => (”1+10” ”2+10” ”3+10” ”4+10”) = (11 12 13 14) (mapcar #’(lambda (x y z) (+ x y z)) ’(1 2 3) ’(10 20 30) ’(100 200 300)) => (111 222 333) We can now define the specialized function by using mapcar instead. Some functions are used for its side-effect: (defun increase-5 (l) (mapcar #’(lambda (n) (+ n 5)) l)) (mapc #’print ’(anna kalle stina)) anna kalle stina (defun first-element (l) (mapcar #’first l)) Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson A function is applied to a list and its tails: Double recurison (maplist #’length ’(a b c d)) => (4 3 2 1) lists in lists binary tree recursion Conditional functions: ”does it hold for every element ..?” ”does it hold for some element ...?” (some #’numberp ’(a 1 b c)) => t (every #’numberp ’(1 2 3 4)) => t (notany #’numberp ’(a b c d)) => t (notevery #’numberp ’(a b c 1 d)) => t Some functions are in if and if-not-variants: (member-if #’numberp ’(a b 1 c d)) => t (remove-if #’numberp ’(a b 1 c d)) => (a b c d) (remove-if-not #’numberp ’(a b 1 c d)) => (1) Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson We make a higher order function which captures this structures. It needs an initial value, a function to apply when the first element is an atom (non-list) and a function to apply when the first element is a list. (defun map-all-elements (l init fn1 fn2) (cond ((endp l) init) ((atom (first l)) (funcall fn1 (first l) (map-all-elements (rest l) init fn1 fn2))) (t (funcall fn2 (map-all-elements (first l) init fn1 fn2) (map-all-elements (rest l) init fn1 fn2)) ))) (map-all-elements '(a (b c (d e)) f) 0 #’(lambda (v1 v2) (+ v2 1)) #’+) => 6 (map-all-elements '(a (b c (d e)) f) ’() #’cons #’append) => ? Example: Total number of elements in a list with arbitrary elements (defun symbols-in-seq (l) (cond ((endp l) 0) ((atom (first l)) (+ 1 (symbols-in-seq (rest l)))) (t (+ (symbols-in-seq (first l)) (symbols-in-seq (rest l))) ))) (symbols-in-seq '(a (b c (d e)) f)) => 6 Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson In lab assignment 3 you will work with graph search with different methods. The basic structure of the algorithm is the same for several of the methods. The difference in the methods are how to evaluate nodes and how to order them on a queue. The main search function is a higher order function and is used by giving a function as parameters for describing the specialized method. Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Lab 2 Pattern matching Pattern matching used in: In this example we will perform matching on lists. We will have the following match symbols: - command languages & matching an arbitrary element to end commands -- match a segment - none, one, two or more elements cl-allegESC or cl-allegTAB using wild-card delete a*.lsp The pattern (a & b &) matches (a x b y), (a a b b) but will not match (b x a y), (a b), (a x b y c) The pattern (a -- b) matches (a b), (a x b), (a x y b), (a 1 2 3 4 5 6 7 8 9 0 b) but will not match (a x y), (a x b y) - search engines in Internet The match may not be unique: The pattern (a -- b --) matches (a x b y b z) by two different ways: (a x b y b z) (a x b y b z) Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Pattern matching The only difficult part is to match a segment. What strategy should we use? Start to let the segment pattern to match no element, try to match the rest of the pattern and the list. If it succeeds we have a match, otherwise let the segment pattern match one element, match the rest, then 2 elements if necessary ... Match (a -- b) with (a x y b) The segment pattern matches no element, try to match (b) with (x y b). Failed. Match the segment pattern with x. Try to match (b) (y b). Failed. Match the segment pattern with x and y. Try to match (b) with (b). Success! When we fail we must ”back track” to the closest segment pattern and let it match next element. We may have several segment patterns, so it may be complicated to follow. A pattern matcher patmatch (defun patmatch (l pat) (cond ((endp pat) (endp l)) ; end of list and pattern? ((eq (first pat) ' --) ; segment pattern (cond ((patmatch l (rest pat)) t) ; match the rest? ((endp l) nil) ; failed, the segment pattern ; matched the entire list (t (patmatch (rest l) pat)))) ; let the segment pattern ; match also next element ; nothing left to match against '&) ; match an arbitrary element ((endp l) nil) ((eq (first pat) (patmatch (rest l) (rest pat))) ((eql (first pat) (first l)) ; elements match (patmatch (rest l) (rest pat))) (t nil)))) ; failed Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Lab 2 Define this pattern matcher and extend it also to do - matching in i arbitrary lists, (lists with lists) - matching ”greater than” and ”less than” of numbers - matching ”not” of a pattern Data base: (((author (anders haraldsson)) (title (programmering i lisp)) (year 1993) (place (room (left 4)))) ((author (anders haraldsson)) (title (programmering i pascal)) (year 1979) (place (room (left 2)))) ((author (nils nilsson)) (title (artificial intelligence)) (year 1999) (place (library (pu 1573)))) ... ) (find-books ’((author (? haraldsson)) --)) => (((author (anders haraldsson)) (title (programmering i lisp)) (year 1993) (place (room (left 4)))) ((author (anders haraldsson)) (title (programmering i pascal)) (year 1979) (place (room (left 2)))) ) How to write Lisp-programs Lisp is an old language and has developed during over 40 years. Old constructs are still in use in the language and new have been added. Today’s standard Common Lisp contains hundreds of functions, often variants of each other. There are attempts to develop code standards, see web-pages for the course. (find-books ’((author (anders haraldsson)) ? (year (> 1985)) --)) => (((author (anders haraldsson)) (title (programmering i lisp)) (year 1993) (place (room (left 4))))) (find-books ’((author (anders haraldsson)) (title (not (-- lisp))) --)) => (((author (anders haraldsson)) (title (programmering i pascal)) (year 1979) (place (room (left 2))))) Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Some ideas and practices nil car - cdr chains The symbol nil is used for different purposes, the false value, the empty list or as the symbol itself. There are alternative external notations to describe it. To access elements in a list the basic functions are car and cdr and we can use combinations such as (cadr l) = (car (cdr l)) An alternative and more readable is to use first and rest instead of car and cdr (they are only synonyms and exactly the same functions). There are also functions for accessing other elements: second, third etc. up to tenth. (add-points ’((kalle 23) (stina 15) (john 20)) => 58 (defun add-points (l) (if (endp l) 0 (+ (second (first l)) (add-points (rest l))))) nil () ’() ’nil false value empty parameter list empty list, to be quoted if the symbol nil is mentioned (defun f () (g ’() ’nil)) (defun g (list symbol) (if (endp list) nil t) The function f has an empty parameter list and calls the function g with an empty list and the symbol nil. The function g returns a false value if the list is empty, otherwise a true value. Use (endp l) to check for the empty list (if you know l is a list), use (null l) if l can be anything and (eq l ’nil) or (equal l ’nil) if l is a symbol. Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Local variables The most common way is to use let (let ((a 1) (b 2) (+ a b)) => 3 (let* ((a 1) (b (+ a 2))) (+ a b)) => 4 Use abstractions (abstract data types, object oriented style) In a real example see lists as representation of a data type or class and introduce ”abstract” primitives as constructors, selectors, recognizers, iterators etc. You can use the way you learnt in other programming language and data structure courses. Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Assume the previous example with add-points and see the problem as defining a data type result-database. Such an object consists of a sequence of results. A result is a record of a name (as a symbol) and points (as an integer). (setq Lisp-results (create-db (make-result ’kalle 23) (make-result ’stina 15) (make-result ’john 20))) (defun make-result (person points) (list person points)) (defun person (result) (first result)) (defun points (result) (second result)) (defun create-db (&rest result-records) result-records) (defun empty-db (result-db) (endp result-db)) (defun first-result (result-db) (first result-db)) (defun rest-result-db (result-db) (rest result-db)) (defun add-points (db) (if (empty-db db) 0 (+ (points (first-result db)) (add-points (rest-result-db db))))) (add-points Lisp-results) => 58 Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Assignment (mutator functions) Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Methodology There are specialized functions to change parts of objects, such as giving a new value to an element in an array or record, or change a pointer in a linked structure (a Lisp list) Common Lisp contains all kind of functions so you may use your favourite paradigm to program. A recommendation is to use generalized assignment (which we find in mostly all languages through = or :=) The most common way is to use Lisp in a functional programming style, writing recursive functions over structures, such as sequences (lists), binary trees, graphs etc. (setf place expression) compare place = expression To us a global variable and assignment of a local variable you may use setq or setf (setq/setf my-pi 3.14) Changing a pointer in a cons-cell in a Lisp list. (setf (first l) p) (setf (rest l) p) Assigning a new value to an element in an array: (setf (aref vector n) 100) In the functional programming style you do not need assignment and special iterative constructs. If you program in a more iterative way there are a lot of constructs such as (dotimes i 5 ...) - repeat 5 times, let i go from 0 to 4 (dolist e ’(a b c d) ...) - for each element in the list (loop .. (return ..) ..) - finish the loop by calling return (do ....) - general iteration Often you may mix paradigms, but try in this course to program as recursive as possible and use iterative construct only when it seems better (may be of efficiency reasons) Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson Program structure The incremental system around Lisp makes it easy to develop smaller units and test them separately. Observe that you do not need any input and output, during the tests. Define first your functions globally, test them, use trace for recursive functions. You can later structure your functions in a block-structured way by using labels. (defun my-reverse (l) (labels ((rev2 (l m) (if (endp l) m (rev2 (rest l) (cons (first l) m))))) (rev2 l ’()))) (my-reverse ‘(a b c)) => (c b a)