Reputation: 6189
I a looking at the third chapter in Practical Common Lisp. In that chapter one creates a database like application. I am stuck at understanding the update
function.
I've written the code in my editor and put comments in for my own understanding of the code:
(defun update (selector-fn &key title artist rating (ripped NIL ripped-p))
(setf ; set ...
*db* ; *DB* to ...
(mapcar ; the value of the application of ...
#'(lambda (row) ; a lambda to rows ...
(when (funcall selector-fn row) ; when a row satisfies ...
; (what does funcall do? if I call the selector function
; why do I still have to check for predicates as below?)
; maybe "when a row is selected"? Is WHEN like a loop over rows?
(if title (setf (getf row :title) title)) ; the title predicate ...
(if artist (setf (getf row :artist) artist)) ; the artist predicate ...
(if rating (setf (getf row :rating) rating)) ; the rating predicate ...
(if ripped-p (setf (getf row :ripped) ripped))) ; and the ripped predicate ...
; why cannot we use our selector function here instead of repeating stuff?!
row) ; why is there a ROW here? isn't the lambda expression already finished?
; maybe the WHEN expression does not return anything and this is a return value of the lambda?
*db*))) ; applies the lambda to the database
Previously a where
function was given:
(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))))
As you can see there are some questions for the code presented in the book. I'll list them below again, but leave the comments in, so that it is clearer, what they relate to.
where
function, instead of writing all those if
expressions again?funcall
(which is not explained in the book in that chapter for that code ...) really calls the selector function, which is the return value, of a call to the where
function given, then why do I have to write all those if
expressions there? Isn't that exactly what the where
function returns? A selector for those rows, which fit the criteria?row
after the when
expression, which seemingly belongs to the lambda
expression? Is that a return value, because the when
expression doesn't return anything, so that the lambda
returns the updated row?I feel like some quite advanced syntax has not been properly explained for this code and I am left guessing how exactly that code works.
An example call to the code would be:
(update (where :artist "artist1") :rating 11)
I tried that, and it really worked.
Here is my "database":
((:TITLE "title3" :ARTIST "artist1" :RATING 10 :RIPPED T)
(:TITLE "title2" :ARTIST "artist2" :RATING 9 :RIPPED T)
(:TITLE "title1" :ARTIST "artist1" :RATING 8 :RIPPED T))
And here is the complete code so far:
(getf (list :a 1 :b 2 :c 3) :b)
(defvar *db* nil)
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
(defun add-record (cd)
(push cd *db*))
(defun dump-db ()
(format t "~{~{~a:~10t~a~%~}~%~}" *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))))
(defun save-db (filename)
(with-open-file
(out filename :direction :output :if-exists :supersede) ; this is a list as parameter! not a function call
; OUT holds the output stream
; opening a file for writing with :DIRECTION :OUTPUT
; if it already exists overrite it :IF-EXISTS :SUPERSEDE
(with-standard-io-syntax (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.
))
(defun load-db (filename)
(with-open-file
(in filename)
; IN contains the input stream
(with-standard-io-syntax (setf *db* (read in)))
; file contains standard syntax of lisp
; SETF sets the value of *DB* to what is read from IN
; WITH-STANDARD-IO-SYNTAX macro ensures that READ is using the same basic
; syntax that save-db did when it PRINTed the data.
))
(defun select-by-artist (artist)
(remove-if-not
#'(lambda (cd) (equal (getf cd :artist) artist))
*db*))
(defun select (selector-fn)
(remove-if-not selector-fn *db*))
(load-db "database")
(dump-db)
; not so general selector function
(defun artist-selector (artist)
#'(lambda (cd) (equal (getf cd :artist) artist)))
; "general" selector function
(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))))
(print (select (where :artist "artist1")))
(defun update (selector-fn &key title artist rating (ripped NIL ripped-p))
(setf ; set ...
*db* ; *DB* to ...
(mapcar ; the value of the application of ...
#'(lambda (row) ; a lambda to rows ...
(when (funcall selector-fn row) ; when a row satisfies ...
; (what does funcall do? if I call the selector function
; why do I still have to check for predicates as below?)
; maybe "when a row is selected"? Is WHEN like a loop over rows?
(if title (setf (getf row :title) title)) ; the title predicate ...
(if artist (setf (getf row :artist) artist)) ; the artist predicate ...
(if rating (setf (getf row :rating) rating)) ; the rating predicate ...
(if ripped-p (setf (getf row :ripped) ripped))) ; and the ripped predicate ...
; why cannot we use our selector function here instead of repeating stuff?!
row) ; why is there a ROW here? isn't the lambda expression already finished?
; maybe the WHEN expression does not return anything and this is a return value of the lambda?
*db*))) ; applies the lambda to the database
Upvotes: 3
Views: 249
Reputation: 27424
The where
function performs a SQL-like “selection” of the elements by evaluating (with and
) the different rows:
(if title (equal (getf cd :title) title) T)
...
to find if a certain “field” has a value specified as parameter to the function. So for instance you can call it with (where :rating 10 :ripped nil)
as explained in the text.
The update
function instead perform a SQL-like “update” of one or more fields. And you should note that the body of the anonymous inner function is completely different from the where
function, since it has lines like:
(if title (setf (getf row :title) title))
...
These are the lines needed to update the “fields”, not to test them. In fact they use setf
, and not equal
. So if we draw a parallel with a generic SQL query, the when
function correspond to the part after the SQL WHERE
:
SELECT *
FROM CDs
WHERE field1 = value1
AND field2 = value2,
...
while, in the update
function, the call to (funcall selector-fn row)
corresponds to whe WHERE
part, while the rows (if ... (setf ...))
corresponds to the SET
part:
UPDATE CDs
SET field1 = value1,
field2 = value2,
...
WHERE field1 = value1
AND field2 = value2,
...
For instance, a call like:
(update (where :artist "artist1") :rating 11)
is equivalent to the SQL query:
UPDATE CDs
SET rating = 11
WHERE artist = 'artist1'
So, what in your comments inside update
is called ; the title predicate
, etc. really should be ; the setting of the title
, etc.
So, the answers to your questions are:
There is no code duplication, the two set of lines perform two different tasks: in where
they are used to filter the elements with equal
, in update
they are used to set the fields with new values.
(funcall f args)
applies the function f
to the arguments args
. So the selector where
is called for each row to see if it satisfies the predicate that filters only the rows that must be modified.
The anonymous function inside update
works in this way: first, if the condition inside the when
is satisfied, updates the row, by performing an assignment through setf
. At the end, it returns row
, that can be modified or not if the selector-fn
has returned true or false. So, the update function updates the *db*
with the values returned by that anonymous function.
Upvotes: 4