Questions about PCL

Discussion of Common Lisp

Questions about PCL

Postby Beltxarga » Sat Apr 05, 2014 8:16 am

Hello,

As I said in a previous post, I am a beginner at programming. I decided to learn Common Lisp and I am currently reading Peter Seibel's Practical Common Lisp. I was thinking I could create a topic for questions about this book, since I already have a question.

I just finished the very interesting and stimulating chapter 3, in which Seibel builds a database for keeping track of CDs. In this chapter, we have a database which is actually a list of lists in a global variable called *db*, and which looks like this:

Code: Select all
((: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))


We want to query the database and update it. For querying, we might do this, for example:

Code: Select all
(remove-if-not #'(lambda (cd)
                   (and (equal (getf cd :title) "Dixie Chicks")
                        (equal (getf cd :rating) 9)))
               *db*)


But building a function that can create directly this anonymous function is better:

Code: Select all
(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))))


We could then create a function like this:

Code: Select all
(defun select (selector-fn)
    (remove-if-not selector-fn *db*))


And write this:

Code: Select all
(select (where :artist "Dixie Chicks"))


But the where function is ugly. A lot of useless code. So Seibel proposes to generate all comparison expressions inside the where function 'on the fly'. He writes two functions:

Code: Select all
(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))))


Then, where can become a nice macro:

Code: Select all
(defmacro where (&rest clauses)
    `#'(lambda (cd) (and ,@(make-comparisons-list clauses))))


But then, Seibel create a function which updates the database, which seems as ugly as the first where function to me (but he doesn't improve it):

Code: Select all
(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*)))


My idea was to modify the make-comparison-expr and make-comparisons-list functions to make them more general: instead of generating a list of equal comparisons, we could generate either a list of equal comparisons or setf assignments by adding a parameter that I called operation. This would look like this:

Code: Select all
(defun make-this-expr (field value operation)
  `(,operation (getf cd ,field) ,value))

(defun make-this-list (fields operation)
  (loop while fields
     collecting (make-this-expr (pop fields) (pop fields) operation)))


This way, the where macro becomes:

Code: Select all
(defmacro where (&rest clauses)
  `#'(lambda (cd) (and ,@(make-this-list clauses 'equal))))


And the update function becomes a similar macro without the initial code duplication and useless conditionals:

Code: Select all
(defmacro update (selector-fn &rest clauses)
  `(setf *db*
         (mapcar
          #'(lambda (cd)
              (when (funcall ,selector-fn cd)
                ,@(make-this-list clauses 'setf))
              cd)
          ,*db*)))


I think it is working. But my question is: is this a bad idea? If it is not, is the code too obscure? I was quite curious as to why Seibel didn't push the logic to the update function by making it a macro.

Thank you for your feedback,

Beltxarga
Beltxarga
 
Posts: 6
Joined: Fri Apr 04, 2014 7:03 am
Location: France

Return to Common Lisp

Who is online

Users browsing this forum: No registered users and 4 guests