PRACTICAL COMMON LISP Peter Seibel 1.

Slides:



Advertisements
Similar presentations
Chapter 3 Functional Programming. Outline Introduction to functional programming Scheme: an untyped functional programming language.
Advertisements

Lecture 2 Introduction to C Programming
Introduction to C Programming
 2000 Prentice Hall, Inc. All rights reserved. Chapter 2 - Introduction to C Programming Outline 2.1Introduction 2.2A Simple C Program: Printing a Line.
Introduction to C Programming
Lists Introduction to Computing Science and Programming I.
Functional Programming COMP2003 A course on functional programming using Common Lisp Dr Eleni Mangina
VBA Modules, Functions, Variables, and Constants
ITERATIVE CONSTRUCTS: DOLIST Dolist is an iterative construct (a loop statement) consisting of a variable declaration and a body The body states what happens.
 2007 Pearson Education, Inc. All rights reserved Introduction to C Programming.
PRACTICAL COMMON LISP Peter Seibel 1.
Introduction to C Programming
COMP 205 – Week 11 Dr. Chunbo Chu. Intro Lisp stands for “LISt Process” Invented by John McCarthy (1958) Simple data structure (atoms and lists) Heavy.
CMPT 120 Lists and Strings Summer 2012 Instructor: Hassan Khosravi.
Fundamentals of Python: From First Programs Through Data Structures
LISP 1.5 and beyond A very quick tour. Data Atoms (symbols) including numbers – All types of numbers including Roman! (well, in the early days) – Syntactically.
Allegro CL Certification Program Lisp Programming Series Level I Session Top Ten Things to Know.
Advanced Functions In CL, functions are often supplied as parameters to other functions –This gives us tremendous flexibility in writing functions whose.
Fundamentals of Python: First Programs
Input/Output Chapters 7 & 9. Output n Print produces output > (print 100) n It also returns the value it printed –that’s where the second 100 came.
Alok Mehta - Programming in Lisp - Data Abstraction and Mapping Programming in Lisp Data Abstraction and Mapping.
PRACTICAL COMMON LISP Peter Seibel 1.
PRACTICAL COMMON LISP Peter Seibel 1.
Mitthögskolan 10/8/ Common Lisp LISTS. Mitthögskolan 10/8/2015 2Lists n Lists are one of the fundamental data structures in Lisp. n However, it.
Common Lisp Macros Read for Input Macros Macro lifetime Macro syntax
Chapter 4: Decision Making with Control Structures and Statements JavaScript - Introductory.
Python Programming Chapter 6: Iteration Saad Bani Mohammad Department of Computer Science Al al-Bayt University 1 st 2011/2012.
Shell Script Programming. 2 Using UNIX Shell Scripts Unlike high-level language programs, shell scripts do not have to be converted into machine language.
Functional Programming and Lisp. Overview In a functional programming language, functions are first class objects. In a functional programming language,
Arrays BCIS 3680 Enterprise Programming. Overview 2  Array terminology  Creating arrays  Declaring and instantiating an array  Assigning value to.
Collecting Things Together - Lists 1. We’ve seen that Python can store things in memory and retrieve, using names. Sometime we want to store a bunch of.
© Copyright 1992–2004 by Deitel & Associates, Inc. and Pearson Education Inc. All Rights Reserved. Chapter 2 Chapter 2 - Introduction to C Programming.
Copyright © 2010 Certification Partners, LLC -- All Rights Reserved Perl Specialist.
Built-in Data Structures in Python An Introduction.
The Loop Macro Many of the CL “functions” are actually macros (let, progn, if, etc) The most complicated macro in CL is probably the Loop macro –The Loop.
Chapter 3: Formatted Input/Output Copyright © 2008 W. W. Norton & Company. All rights reserved. 1 Chapter 3 Formatted Input/Output.
Python uses boolean variables to evaluate conditions. The boolean values True and False are returned when an expression is compared or evaluated.
Predicates, Functions and Files "I suppose I should learn Lisp, but it seems so foreign." - Paul Graham, Nov 1983.
Introduction to LISP. Lisp Extensible: It lets you define new operators yourself Lisp programs are expressed as lisp data structures –You can write programs.
PRACTICAL COMMON LISP Peter Seibel 1.
UMBC CMSC Common Lisp II. UMBC CMSC Input and Output Print is the most primitive output function > (print (list 'foo 'bar)) (FOO BAR) The.
PRACTICAL COMMON LISP Peter Seibel 1.
Copyright © 2003 ProsoftTraining. All rights reserved. Perl Fundamentals.
Files Tutor: You will need ….
Operating on Lists Chapter 6. Firsts and Seconds n Transforming list of pairs into two lists –(firsts ‘((1 5) (2 6) (3 7)))  (1 2 3) –(seconds ‘((1 5)
Milos Hauskrecht (PDF) Hieu D. Vu (PPT) LISP PROGARMMING LANGUAGE.
1 Variable Declarations Global and special variables – (defvar …) – (defparameter …) – (defconstant …) – (setq var2 (list 4 5)) – (setf …) Local variables.
8 Chapter Eight Server-side Scripts. 8 Chapter Objectives Create dynamic Web pages that retrieve and display database data using Active Server Pages Process.
© Copyright 1992–2004 by Deitel & Associates, Inc. and Pearson Education Inc. All Rights Reserved. 1 Chapter 2 - Introduction to C Programming Outline.
Basic Introduction to Lisp
PRACTICAL COMMON LISP Peter Seibel 1.
PRACTICAL COMMON LISP Peter Seibel 1.
Chapter 3: Formatted Input/Output 1 Chapter 3 Formatted Input/Output.
FILES AND EXCEPTIONS Topics Introduction to File Input and Output Using Loops to Process Files Processing Records Exceptions.
1 Lecture 2 - Introduction to C Programming Outline 2.1Introduction 2.2A Simple C Program: Printing a Line of Text 2.3Another Simple C Program: Adding.
Clojure Macros. Homoiconicity All versions of Lisp, including Clojure, are homoiconic This means that there is no difference between the form of the data.
1 Outline Review Introduction to LISP Symbols and Numbers Lists Writing LISP Functions LISPWorks.
Defining Macros in Lisp
Chapter 2 - Introduction to C Programming
Getting Started with Lisp
Scripts & Functions Scripts and functions are contained in .m-files
Topics Introduction to File Input and Output
J.E. Spragg Mitthögskolan 1997
Defining Macros in Lisp
Clojure Macros.
Peter Seibel Practical Common Lisp Peter Seibel
Common Lisp II.
Topics Introduction to File Input and Output
Introduction to C Programming
The general format of case is the following: (case <key form>
Presentation transcript:

PRACTICAL COMMON LISP Peter Seibel 1

CHAPTER 3 PRACTICAL: A SIMPLE DATABASES 2

FUNCTIONS IN THIS CHAPTER (defvar *db* nil) (defun make-cd (title artist rating ripped) (list :title title :artist artist :rating rating :ripped ripped)) (defun dump-db () (dolist (cd *db*) (format t "~{~a:~10t~a~%~}~%" cd))) (defun add-record (cd) (push cd *db*)) (defun prompt-read (prompt) (format *query-io* "~a: " prompt) (force-output *query-io*) (read-line *query-io*)) (defun prompt-for-cd () (make-cd (prompt-read "Title") (prompt-read "Artist") (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0) (y-or-n-p "Ripped [y/n]: "))) (defun add-cds () (loop (add-record (prompt-for-cd)) (if (not (y-or-n-p "Another? [y/n]: ")) (return)))) 3

FUNCTIONS IN THIS CHAPTER (defun save-db (filename) (with-open-file (out filename :direction :output :if-exists :supersede) (with-standard-io-syntax (print *db* out)))) (defun load-db (filename) (with-open-file (in filename) (with-standard-io-syntax (setf *db* (read in))))) (defun update (selector-fn &key title artist rating (ripped nil ripped-p)) (setf *db* (mapcar #'(lambda (row) (when (funcall selector-fn row) (if title (setf (getf row :title) title)) (if artist (setf (getf row :artist) artist)) (if rating (setf (getf row :rating) rating)) (if ripped-p (setf (getf row :ripped) ripped))) row) *db*))) 4

FUNCTIONS IN THIS CHAPTER (defun select (selector-fn) (remove-if-not selector-fn *db*)) (defun delete-rows (selector-fn) (setf *db* (remove-if selector-fn *db*))) (defun make-comparison-expr (field value) '(equal (getf cd,field),value)) (defun make-comparisons-list (fields) (loop while fields collecting (make-comparison-expr (pop fields) (pop fields)))) (defmacro where (&rest clauses) '#'(lambda (cd) clauses)))) 5

CDS AND RECORDS In this chapter you will write a simple database for keeping track of CDs. LIST function: CL-USER>(list 1 2 3) (1 2 3) LIST function makes a list and returns a list of its arguments. Property list (plist): a list where every other element, starting with the first, is a symbol (keyword symbol) that describes what the next element in the list is. CL-USER>(list :a 1 :b 2 :c 3) (:A 1 :B 2 :C 3) 6

CDS AND RECORDS GETF function: (Getf place key default(optional)) => value CL-USER> (getf (list :a 1 :b 2 :c 3) :a) 1 GETF function takes a plist and a symbol and returns the value in the plist following the symbol. (getf '(a b 4 d a x) 'a) => B (getf '(a b 4 d a x) 'x) => NIL (getf '(a b 4 d a x) 'x 'not-found) => NOT-FOUND (getf '(a b 4 d a x) 4 'not-found) => D 7

CDS AND RECORDS Now, we can write a function make-cd that will take the four fields as arguments and return a plist representing that CD. (defun make-cd (title artist rating ripped) (list :title title :artist artist :rating rating :ripped ripped)) Example to make a record for the CD Roses by Kathy Mattea: ( make-cd "Roses" "Kathy Mattea" 7 t) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T) PS: Ripping is the process of copying audio or video content to a hard disk, typically from removable media such as CD or DVD, although the word refers to all forms of media. 8

FILING CDS DEFVAR macro: define a global variable, *db*. The asterisks (*) in the name are a Lisp naming convention( 慣例 ) for global variables. (defvar *db* nil) PUSH macro: add items to *db*. (defun add-record (cd) (push cd *db*)) PUSH macro can add an element to a list. The name ''push'' comes from classical computer science terminology for stacks. POP macro can delete an element from a list. 9

FILING CDS Use add-record and make-cd to add CDs to the database. CL-USER> (add-record (make-cd "Roses" "Kathy Mattea" 7 t)) ((:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) CL-USER> (add-record (make-cd "Fly" "Dixie Chicks" 8 t)) ((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) CL-USER> (add-record (make-cd "Home" "Dixie Chicks" 9 t)) ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) Looking at the database contents. CL-USER> *db* ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) 10

LOOKING AT THE DATABASE CONTENTS Dump database: (defun dump-db () (dolist (cd *db*) (format t "~{~a:~10t~a~%~}~%" cd))) Example: CL-USER>(dump-db) TITLE: Home ARTIST: Dixie Chicks RATING: 9 RIPPED: T TITLE: Fly ARTIST: Dixie Chicks RATING: 8 RIPPED: T TITLE: Roses ARTIST: Kathy Mattea RATING: 7 RIPPED: T 11

 DOLIST macro: bind each element to the variable cd in turn.  For each value of cd, the FORMAT function can print it. (DOLIST (index-var list [result-form]) body)  For example: > (dolist (x '(red blue green) 'flowers) (format t "~&Roses are ~S." x)) Roses are RED. Roses are BLUE. Roses are GREEN. FLOWERS PS: ~% causes FORMAT to begin a new line. ~& begins a new line only if not already at the beginning of a new line. ~S inserts the printed representation of a Lisp object into the message that FORMAT prints. 12

 DOTIMES macro: evaluate the forms in its body n times, while stepping an index variable from zero through n-1. It then returns the value of result-form, which defaults to NIL if omitted. (DOTIMES (index-var n [result-form]) body) For example: > (dotimes (i 4) (format t "~&I is ~S." i)) I is 0. I is 1. I is 2. I is 3. NIL 13

 The RETURN function can be used to exit the body of an iteration form immediately, without looping any further.  RETURN takes one input: the value to return as the result of the iteration form.  When RETURN is used to force an exit from an iteration form, the result-form expression, if any, is ignored.  For example: (defun find-first-odd (list-of-numbers) (dolist (e list-of-numbers) (format t "~&Testing ~S..." e) (when (oddp e) (format t "found an odd number.") (return e)))) 14 (DOLIST (index-var list [result-form]) body)

> (find-first-odd ’( )) ;Will never reach 8. Testing 2... Testing 4... Testing 6... Testing 7...found an odd number. 7 > (find-first-odd ’( )) Testing 2... Testing 4... Testing 6... Testing 8... Testing NIL 15

(defun check-all-odd (list-of-numbers) (dolist (e list-of-numbers t) (format t "~&Checking ~S..." e) (if (not (oddp e)) (return nil)))) > (check-all-odd ’(1 3 5)) Checking 1... Checking 3... Checking 5... T > (check-all-odd ’( )) Checking 1... Checking 3... Checking 4... NIL 16 (DOLIST (index-var list [result-form]) body)

 Write an iterative version of the MEMBER function, called IT_MEMBER. It should return T if its first input appears in its second input; it need not return a sublist of its second input. > (it_member '3 '( )) T  What are the meanings of the following functions? (defun it1 (x) (let ((n 0)) (dolist (e x n) (incf n)))) (defun it2 (n x) (dotimes (i n (first x)) (pop x))) 17

LOOKING AT THE DATABASE CONTENTS FORMAT function: CL-USER> (format t "~a" "Dixie Chicks") Dixie Chicks NIL CL-USER> (format t "~a" :title) TITLE NIL ~a is the aesthetic ( 美學的 ) directive ( 指令 ) ; it means to consume one argument and output it in a human-readable form. ~t is for tabulating. ~10t tells FORMAT to emit enough spaces to move to the tenth column before processing the next ~a. 18

LOOKING AT THE DATABASE CONTENTS CL-USER> (format t "~a:~10t~a" :artist "Dixie Chicks") ARTIST:Dixie Chicks NIL (defun dump-db () (format t "~{~{~a:~10t~a~%~}~%~}" *db*)) The ~% directive doesn't consume any arguments but tells FORMAT to emit a newline. The last ~% tells FORMAT to emit one more newline to put a blank line between each CD. 19

IMPROVING THE USER INTERACTION Add CDs by users : (defun prompt-read (prompt) (format *query-io* "~a: " prompt) (force-output *query-io*) (read-line *query-io*)) (defun prompt-for-cd () (make-cd (prompt-read "Title") (prompt-read "Artist") (prompt-read "Rating") (prompt-read "Ripped [y/n]"))) (defun add-cds () (loop (add-record (prompt-for-cd)) (if (not (y-or-n-p "Another? [y/n]: ")) (return)))) 20

IMPROVING THE USER INTERACTION (defun prompt-read (prompt) (format *query-io* "~a: " prompt) (force-output *query-io*) (read-line *query-io*)) FORCE-OUTPUT ensures that Lisp doesn't wait for a newline before it prints the prompt. (depending on compilers) READ-LINE function: read a single line of text. The variable *query-io* is a global variable that contains the input stream connected to the terminal. The return value of prompt-read will be the value of the last form, the call to READ-LINE, which returns the string it read. 21

IMPROVING THE USER INTERACTION (defun prompt-for-cd () (make-cd (prompt-read "Title") (prompt-read "Artist") (prompt-read "Rating") (prompt-read "Ripped [y/n]"))) We can combine the existing make-cd function with prompt-read to build a function that makes a new CD record from data it gets by prompting for each value in turn. That's almost right. Except prompt-read returns a string, which, while fine for the Title and Artist fields, isn't so great for the Rating and Ripped fields, which should be a number and a boolean. 22

IMPROVING THE USER INTERACTION (parse-integer (prompt-read "Rating") :junk-allowed t) PARSE-INTEGER function: The default behavior of PARSE-INTEGER is to signal an error if it can‘t parse an integer out of the string or if there’s any non-numeric junk ( 垃 圾 ) in the string. However, it takes an optional keyword argument :junk-allowed, which tells it to relax a bit. If it can‘t find an integer amidst( 在 … 中間 ) all the junk, PARSE- INTEGER will return NIL rather than a number. In keeping with the quick-and-dirty ( 應急的 ) approach, you may just want to call that 0 and continue. Lisp's OR macro is just the thing you need here. (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0) 23

PARSE-INTEGER Break 8 [14]> (parse-integer "3") 3 ; 1 Break 8 [14]> (parse-integer " 3") 3 ; 2 Break 8 [14]> (parse-integer " 3 ") 3 ; 4 Break 8 [14]> (parse-integer " 3 a b ") PARSE-INTEGER: substring " 3 a b " does not have integer syntax at position 3 Break 10 [16]> (parse-integer " 3 " :junk-allowed t) 3 ; 2 Break 10 [16]> (parse-integer " 3 a b " :junk-allowed t) 3 ; 4 Break 10 [16]> (parse-integer " 35688ab " :junk-allowed t) ; 8 24

IMPROVING THE USER INTERACTION Y-OR-N-P function (y-or-n-p "Ripped [y/n]: ") Y-OR-N-P will reprompt ( 重新提示 ) the user if they enter something that doesn't start with y, Y, n, or N. (defun add-cds () (loop (add-record (prompt-for-cd)) (if (not (y-or-n-p "Another? [y/n]:” )) (return)))) LOOP macro repeatedly executes a body of expressions until it's exited by a call to RETURN. 25

IMPROVING THE USER INTERACTION Add CDs by users (the finial version) : (defun prompt-read (prompt) (format *query-io* "~a: " prompt) (force-output *query-io*) (read-line *query-io*)) (defun prompt-for-cd () (make-cd (prompt-read "Title") (prompt-read "Artist") (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0) (y-or-n-p "Ripped [y/n]: "))) (defun add-cds () (loop (add-record (prompt-for-cd)) (if (not (y-or-n-p "Another? [y/n]: ")) (return)))) 26

IMPROVING THE USER INTERACTION CL-USER> (add-cds) Title: Rockin' the Suburbs Artist: Ben Folds Rating: 6 Ripped [y/n]: y Another? [y/n]: y Title: Give Us a Break Artist: Limpopo Rating: 10 Ripped [y/n]: y Another? [y/n]: y Title: Lyle Lovett Artist: Lyle Lovett ( 萊爾拉維特 ) Rating: 9 Ripped [y/n]: y Another? [y/n]: n NIL 27

SAVING AND LOADING THE DATABASE Save the current state of the database: (defun save-db (filename) (with-open-file (out filename :direction :output :if-exists :supersede) (with-standard-io-syntax (print *db* out)))) The WITH-OPEN-FILE macro opens a file, binds the stream to a variable, executes a set of expressions, and then closes the file. Here we specify that we‘re opening the file for writing with :direction :output and that we want to overwrite an existing file of the same name if it exists with :if-exists :supersede ( 取代 ). Once we have the file open, all we have to do is print the contents of the database with (print *db* out). The macro WITH-STANDARD-IO-SYNTAX ensures that certain variables that affect the behavior of PRINT are set to their standard values. 28

SAVING AND LOADING THE DATABASE Example: CL-USER> (save-db "C:/CLISP/my-cds.db") ((:TITLE "Lyle Lovett" :ARTIST "Lyle Lovett" :RATING 9 :RIPPED T) (:TITLE "Give Us a Break" :ARTIST "Limpopo" :RATING 10 :RIPPED T) (:TITLE "Rockin' the Suburbs" :ARTIST "Ben Folds" :RATING 6 :RIPPED T) (:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 9 :RIPPED T)) 29

SAVING AND LOADING THE DATABASE Load the database: (defun load-db (filename) (with-open-file (in filename) (with-standard-io-syntax (setf *db* (read in))))) This time we don't need to specify :direction in the options to WITH- OPEN-FILE, since we want the default of :input. READ function can be used to read from the stream in. The SETF macro is Common Lisp's main assignment operator. It sets its first argument to the result of evaluating its second argument. CL-USER> (load-db "C:/CLISP/my-cds.db") 30

QUERYING THE DATABASE REMOVE-IF-NOT function: The function REMOVE-IF-NOT takes a predicate and a list and returns a list containing only the elements of the original list that match the predicate. It has removed all the elements that don't match the predicate. Example: CL-USER> (remove-if-not #'evenp '( )) ( ) Function EVENP returns true if its argument is an even number. Notation #' means “Get me the function with the following name.” 31

QUERYING THE DATABASE If EVENP didn't exist: CL-USER> (remove-if-not #'evenp '( )) CL-USER> (remove-if-not #'(lambda (x) (= 0 (mod x 2))) '( )) ( ) (lambda (x) (= 0 (mod x 2))) which checks that its argument is equal to 0 modulus 2 (in other words, is even). For example: CL-USER> (remove-if-not #'(lambda (x) (= 1 (mod x 2))) '( )) ( ) Note that lambda isn't the name of the function—it's the indicator you're defining an anonymous function. 32

QUERYING THE DATABASE To select all the Dixie Chicks‘ albums ( 狄克西女子合唱團的唱片 ) in the database: CL-USER> (remove-if-not #'(lambda (cd) (equal (getf cd :artist) "Dixie Chicks")) *db*) ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)) Assume that cd is the name of a variable holding a single database record, (getf cd :artist) can be used to extract the name of the artist. The function EQUAL compares them character by character. (equal (getf cd :artist) "Dixie Chicks") tests whether the artist field of a given CD is equal to "Dixie Chicks". 33

QUERYING THE DATABASE To wrap ( 包裹 ) that whole expression in a function that takes the name of the artist as an argument: (defun select-by-artist (artist) (remove-if-not #'(lambda (cd) (equal (getf cd :artist) artist)) *db*) ) A more general select function that takes a function as an argument: (defun select (selector-fn) (remove-if-not selector-fn *db*)) CL-USER> (select #'(lambda (cd) (equal (getf cd :artist) "Dixie Chicks"))) ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)) 34

QUERYING THE DATABASE Another way: (defun artist-selector (artist) #'(lambda (cd) (equal (getf cd :artist) artist))) CL-USER> (select (artist-selector "Dixie Chicks")) ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)) If you call artist-selector with an argument of "Dixie Chicks", you get an anonymous function that matches CDs whose :artist field is "Dixie Chicks”. 35

QUERYING THE DATABASE The following function has three parameters and must be called with three arguments: (defun foo (a b c) (list a b c)) (foo 1 2 3) → (1 2 3) A version of foo that uses keyword parameters: (defun foo (&key a b c) (list a b c)) The calls to this new foo will look quite different. These are all legal calls with the result to the right of the → : (foo :a 1 :b 2 :c 3) → (1 2 3) (foo :c 3 :b 2 :a 1) → (1 2 3) (foo :a 1 :c 3) → (1 NIL 3) (foo) → (NIL NIL NIL) 36

QUERYING THE DATABASE An version of foo that uses supplied-p parameters : (defun foo (&key a (b 20) (c 30 c-p)) (list a b c c-p)) (foo :a 1 :b 2 :c 3) → (1 2 3 T) (foo :c 3 :b 2 :a 1) → (1 2 3 T) (foo :a 1 :c 3) → ( T) (foo) → (NIL NIL) When you specify a keyword parameter you can replace the simple name with a list consisting of the name of the parameter, a default value, and another parameter name, called a supplied-p parameter. The supplied-p parameter will be set to true or false depending on whether an argument was actually passed for that keyword parameter in a particular call to the function. 37

QUERYING THE DATABASE The general selector-function generator is a function that takes four keyword parameters corresponding to the fields in our CD records and generates a selector function that selects any CDs that match all the values given to where. For examples: (select (where :artist "Dixie Chicks")) (select (where :rating 10 :ripped nil)) The function where can be defined as: (defun where (&key title artist rating (ripped nil ripped-p)) #'(lambda (cd) (and (if title (equal (getf cd :title) title) t) (if artist (equal (getf cd :artist) artist) t) (if rating (equal (getf cd :rating) rating) t) (if ripped-p (equal (getf cd :ripped) ripped) t)))) 38

QUERYING THE DATABASE (defun where (&key title artist rating (ripped nil ripped-p)) #'(lambda (cd) (and (if title (equal (getf cd :title) title) t) (if artist (equal (getf cd :artist) artist) t) (if rating (equal (getf cd :rating) rating) t) (if ripped-p (equal (getf cd :ripped) ripped) t)))) The function WHERE returns an anonymous function that returns the logical AND of one clause per field in our CD records. Each clause checks if the appropriate argument was passed in and then either compares it to the value in the corresponding field in the CD record or returns t, if the parameter wasn't passed in. Thus, the selector function will return t only for CDs that match all the arguments passed to where. 39

 WHEN and UNLESS are conditional forms to evaluate more than one expression when a test is true.  Their syntax is: (WHEN test body) (UNLESS test body)  WHEN first evaluates the test form.  If the result is NIL, WHEN just returns NIL.  If the result is non-NIL, WHEN evaluates the forms in its body and returns the value of the last one.  UNLESS is similar, except it evaluates the forms in its body only if the test is false. 40

(defun picky-multiply (x y) "Computes X times Y. Input X must be odd; Y must be even." (unless (oddp x) (incf x) (format t "~&Changing X to ~S to make it odd." x)) (when (oddp y) (decf y) (format t "~&Changing Y to ~S to make it even." y)) (* x y)) > (picky-multiply 4 6) Changing X to 5 to make it odd. 30 > (picky-multiply 2 9) Changing X to 3 to make it odd. Changing Y to 8 to make it even

UPDATING EXISTING RECORDS— ANOTHER USE FOR WHERE Update Existing Records: (defun update (selector-fn &key title artist rating (ripped nil ripped-p)) (setf *db* (mapcar #'(lambda (row) (when (funcall selector-fn row) ; find the existing records (if title (setf (getf row :title) title) ) ; update title (if artist (setf (getf row :artist) artist) ) ; update artist (if rating (setf (getf row :rating) rating)) ; update rating (if ripped-p (setf (getf row :ripped) ripped))) ; update ripped row) *db*))) Break 4 [7]> (mapcar #'(lambda (row) (setf row 100)) '( )) ( ) Break 4 [7]> (mapcar #'(lambda (row) row) '( )) ( ) 42

UPDATING EXISTING RECORDS— ANOTHER USE FOR WHERE The update function is used to update a set of records matching a particular where clause. Function MAPCAR maps over a list, *db* in this case, and returns a new list containing the results of calling a function on each item in the original list. SETF is a general assignment operator that can be used to assign lots of “places” other than just variables. (setf (getf row :title) title) The plist referenced by row will have the value of the variable title following the property name :title. GETF function takes a plist and a symbol and returns the value in the plist following the symbol. 43

UPDATING EXISTING RECORDS— ANOTHER USE FOR WHERE Running example: ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)) CL-USER> (update (where :artist "Dixie Chicks") :rating 11) NIL CL-USER> (select (where :artist "Dixie Chicks")) ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 11 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 11 :RIPPED T)) 44

UPDATING EXISTING RECORDS— ANOTHER USE FOR WHERE To delete rows from the database: (defun delete-rows (selector-fn) (setf *db* (remove-if selector-fn *db*))) The function REMOVE-IF returns a list with all the elements that do match the predicate removed. Running example: CL-USER> (delete-rows (where :artist "Dixie Chicks")) CL-USER> (select (where :artist "Dixie Chicks")) NIL 45

REMOVING DUPLICATION AND WINNING BIG REVERSE function: REVERSE takes a list as an argument and returns a new list that is its reverse. For example: CL-USER>(reverse '(1 2 3)) (3 2 1) Define a macro: (defmacro backwards (expr) (reverse expr)) A macro definition consists of a name, a parameter list, and a body of expressions, like a function. However, a macro has a totally different effect. For example: CL-USER>(backwards ("hello, world" t format)) hello, world NIL 46

REMOVING DUPLICATION AND WINNING BIG CL-USER>(backwards ("hello, world" t format)) hello, world NIL How did that work? When the REPL started to evaluate the backwards expression, it recognized that backwards is the name of a macro. So it left the expression ("hello, world" t format) unevaluated, which is good because it isn't a legal Lisp form. It then passed that list to the backwards code. The code in backwards passed the list to REVERSE, which returned the list (format t "hello, world"). backwards then passed that value back out to the REPL, which then evaluated it in place of the original expression. In other words, the compiler will generate exactly the same code whether you write (backwards ("hello, world" t format)) or (format t "hello, world"). 47

REMOVING DUPLICATION AND WINNING BIG Write a macro that generates exactly the code we need for each particular call to where. For example: (equal (getf cd field) value) (defun make-comparison-expr (field value) (list 'equal (list 'getf 'cd field) value)) Stick a single forward quote (') in front of a function or a variable: to stop Lisp from evaluating a form. CL-USER> (make-comparison-expr :rating 10) (EQUAL (GETF CD :RATING) 10) CL-USER> (make-comparison-expr :title "Give Us a Break") (EQUAL (GETF CD :TITLE) "Give Us a Break") 48

REMOVING DUPLICATION AND WINNING BIG A back quote (`) before an expression stops evaluation just like a forward quote. CL-USER> `(1 2 3) (1 2 3) CL-USER> '(1 2 3) (1 2 3) However, in a back-quoted expression, any subexpression that's preceded by a comma is evaluated. Notice the effect of the comma in the second expression: `(1 2 (+ 1 2)) → (1 2 (+ 1 2)) `(1 2,(+ 1 2)) → (1 2 3) Using a back quote, you can write make-comparison-expr like this: (defun make-comparison-expr (field value) `(equal (getf cd,field),value)) 49

REMOVING DUPLICATION AND WINNING BIG (defun make-comparison-expr (field value) `(equal (getf cd,field),value)) [2]> *db* ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) [3]> (make-comparison-expr :rating 9) (EQUAL (GETF CD :RATING) 9) [4]> (make-comparison-expr 'rating 9) (EQUAL (GETF CD RATING) 9) [5]> (make-comparison-expr ':rating 9) (EQUAL (GETF CD :RATING) 9) 50

 COLLECTING creates a list and returns it as the value of the loop. [2]> (loop for x from 1 to 5 for y = (* x 2) collecting y) ( ) [2]> (loop for x in '(a b c d e) for y in '( ) collecting (list x y) ) ((A 1) (B 2) (C 3) (D 4) (E 5)) [2]> (loop for I from 1 to 5 collecting (* I I)) ( ) 51

REMOVING DUPLICATION AND WINNING BIG (defun make-comparisons-list (fields) (loop while fields collecting (make-comparison-expr (pop fields) (pop fields)))) ; make-comparison-expr pop two records at a time (defun make-comparison-expr (field value) `(equal (getf cd,field),value)) This LOOP expression loops while there are elements left in the fields list, popping off two at a time, passing them to make-comparison-expr, and collecting the results to be returned at the end of the loop. The POP macro performs the inverse operation of the PUSH macro you used to add records to *db*. 52

REMOVING DUPLICATION AND WINNING BIG [2]> *db* ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) [3]> (make-comparisons-list *db*) ((EQUAL (GETF CD (:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)) (EQUAL (GETF CD (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) NIL)) 53

REMOVING DUPLICATION AND WINNING BIG Now, we can define the where macro: (defmacro where (&rest clauses) `#'(lambda (cd) clauses)))) Using a back quote to make a template that you fill in by interpolating the value of make-comparisons-list, it's trivial. This macro uses a variant of, (namely, before the call to make- comparisons-list. “splices”( 接合 ) the value of the following expression—which must evaluate to a list—into the enclosing list. `(and,(list 1 2 3)) → (AND (1 2 3)) 1 2 3)) → (AND 1 2 3) 1 2 3) 4) → (AND ) 54

REMOVING DUPLICATION AND WINNING BIG (defmacro where (&rest clauses) `#'(lambda (cd) clauses)))) Like &key, &rest modifies the way arguments are parsed. With a &rest in its parameter list, a function or macro can take an arbitrary number of arguments, which are collected into a single list that becomes the value of the variable whose name follows the &rest. So if you call where like this: (where :title "Give Us a Break" :ripped t) the variable clauses will contain the list. (:title "Give Us a Break" :ripped t) This list is passed to make-comparisons-list, which returns a list of comparison expressions. CL-USER> (macroexpand-1 '(where :title "Give Us a Break" :ripped t)) #'(LAMBDA (CD) (AND (EQUAL (GETF CD :TITLE) "Give Us a Break") (EQUAL (GETF CD :RIPPED) T))) T 55

EXAMPLES [2]> *db* ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) [3]> (where :title "Give Us a Break" :ripped t) #<FUNCTION :LAMBDA (CD) (AND (EQUAL (GETF CD :TITLE) "Give Us a Break") (EQUAL (GETF CD :RIPPED) T))) [4]> (select (where :title "Give Us a Break" :ripped t)) NIL [5]> (select (where :title "Home" :ripped t)) ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)) [6]> (delete-rows (where :title "Home" :ripped t)) ((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) [7]> (select (where :title "Home" :ripped t)) NIL 56