Example and Discussion of common lisp code ported to emacs lisp

In this page I (Paul Horton) provide an elisp port of code found in Chapter 3. Practical: A Simple Database of Peter Seibel's nice book Practical Common Lisp.
The ideas here are copied from Peter Seibel. To make sense of this page, you will need to read his chapter in parallel.

Common Lisp format

Seibel's code uses Common Lisp format, to format strings.
Fortunately much of the Common Lisp format functionality is available in Elisp with cl-format.

Note that while the Elisp and Common Lisp format commands are similar in the broad sense; in detail they are completely different.
The elisp format specification mainly shadows printf from C; while Common Lisp format defines its own elaborate string formatting language.

Where to learn more about Common lisp format?

Chapter 18 of Seibel's book gives a good introduction to a useful subset of Common Lisp format's formidable repertoire of directives.
There is also a Wikipedia page on Common Lisp Format.
For a more formal specification, see the section on format in Guy Steele's Common Lisp the Language, 2nd Edition.

As for information specific to the Elisp version;
I have not seen info pages for cl-format, but it does have an especially detailed docstring (use describe-function to see it).

Preliminaries

Make sure you are using lexical binding

Some of the code requires lexical binding to be turned on to work properly.
One convenient way to do that is to run the command elisp-enable-lexical-binding in the (file visiting) buffer you plan to paste the code into.
It should add:   -*- lexical-binding: t; -*-   to the buffer head line.

To confirm that lexical binding is on in the buffer, you can use eval-expression to see the value of the variable lexical-binding.
Note that variable is buffer local so its value may differ from buffer to buffer.

Installing cl-format

First require cl-lib for function such as cl-remove-if-not and cl-format for an approximation of the common lisp format function.
cl-format can be obtained from the elpa package repository. I use use-package to load it.
Just in case you have not installed use-package, you can install it like this:
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
And then use use-package to install cl-format:
(use-package cl-format :ensure t)
So programs can require it.
(require 'cl-format)

Initial definitions

(defun pcl/make-cd (title artist rating ripped)
  (list :title title :artist artist :rating rating :ripped ripped))

(defvar pcl/*db* nil)

(defun pcl/add-record (cd) (push cd pcl/*db*))
These are identical to Seibel's common lisp version; except I have added a prefix "pcl/" to the function and global variable names.
Prefixing names like This is a standard practice in elisp to show that they belong together - and to avoid name collisions.

I use a slash "pcl/..." to separate the prefix and the rest of the name.
An alternative (and more common) style would be using a hyphen, as in "pcl-make-cd.
I think "pcl/make-cd" is nicer because it visually separates the prefix pcl from the rest of the name.
Note however that (require 'pcl/package) would be problematic, since it implies a filename containing a '/' character.

(progn
  (pcl/add-record (pcl/make-cd "Roses" "Kathy Mattea" 5 t))
  (pcl/add-record (pcl/make-cd "Fly"  "Dixie Chicks" 6 t))
  (pcl/add-record (pcl/make-cd "Home" "Dixie Chicks" 7 t))
  (pcl/add-record (pcl/make-cd "Give Us a Break" "Limpopo" 10 t))
  (pcl/add-record (pcl/make-cd "玻璃心" "黃明志 & 陳芳語" 9 t))
  )
Also the same, except I added a non-ascii name just for fun.
(defvar pcl/db-display-buffer*)
Seibel defines a function dump-db to dump the contents of the database to standard out.
Since we are using emacs, I chose to output messages to a buffer pcl/db-display-buffer*
displayed in a window.
(defun pcl/display-entries (db-entries)
  "Show contents of database DB-ENTRIES in pcl/db-display* buffer"
  (if (not db-entries)
      (message "No entries to show.")
    (setq pcl/db-display-buffer* (get-buffer-create "pcl/db-display-buffer*"))
    (pop-to-buffer pcl/db-display-buffer*)
    (let  (buffer-read-only)   ;; temporarily set buffer-read-only to nil.
      (erase-buffer)
      (cl-format pcl/db-display-buffer* "~{~{~a:~10t~a~%~}~%~}" db-entries)
      (goto-char (point-min))
      )
    (special-mode)
    (view-mode 1)
  ))

(defun pcl/display-db ()
  "Display CD database in a display buffer."
  (interactive)
  (pcl/display-entries pcl/*db*)
  )
interactive declares functions to be emacs commands, which can be invoked by name after pressing M-x.

The two lines involving the major mode special-mode and the minor mode view-mode
are so that the buffer will be displayed in a mode suitable for viewing but not editing.
So, for example, pressing the 'q' key should invoke View-quit instead of inserting a "q" into the buffer.

The (let (buffer-read-only)...) block allows the encapsulated code to alter the buffer even if the buffer was in special-mode before entering the block.

(defun pcl/prompt-for-cd ()
  "Prompt user for CD info:  title, artist, rating, and ripped?"
  (pcl/make-cd
   (read-string "title: ")
   (read-string "artist: ")
   (read-number "rating: ")
   (y-or-n-p "ripped? (y/n)")
   ))

(defun pcl/add-cds ()
  "Add one or more CDs to database."
  (interactive)
  (pcl/add-record (pcl/prompt-for-cd))
  (while (progn (sleep-for 0.2);  Pause at end of record input.
                (y-or-n-p "Enter another cd? (y/n)"))
    (pcl/add-record (pcl/prompt-for-cd))
    ))
Pretty straightforward port of Seibel's common lisp code
In lieu of the common lisp functions prompt-read and parse-integer, I use elisp read-string, read-number functions to read from the emacs mini-buffer.
The (sleep-for 0.2) in pcl/add-cds is worth mentioning.
It simply pauses for 0.2 seconds before y/n asking if the user wants to input another cd.

This delay between the questions "Ripped?" and "Enter another cd?" is useful in the elisp version,
Without the sleep-for delay, the second question appears instantaneously after the first,
because elisp y-or-n-p responds immediately when the user presses the y or n key (without waiting for the user to hit enter).
As a user, I found that confusing. Try removing the sleep-for to see what I mean.

If you are cut-and-pasting the code into emacs and evaluating it as your read,
try running pcl/add-cds now to add a few of your favorite CDs.
Maybe Queen's 1975 album "A Night at the Opera" :-)
Then use pcl/prompt-for-cd to view the results.

Saving and Loading the Database

(defun pcl/save-db (filename)
  (interactive "FDatabase filename: ")
  (with-temp-file filename
      (print pcl/*db* (current-buffer))
      ))

(defun pcl/load-db (filename)
  (interactive "fDatabase filename: ")
  (with-temp-buffer
    (insert-file-contents filename)
    (setq pcl/*db* (read (current-buffer)))
    ))
Note the use of intermediate buffers here instead of standard in/out to print and read to files.
Elisp is buffer-centric.

Querying the Database

(defun pcl/select-by-artist (artist)
  (cl-remove-if-not
   #'(lambda (cd) (equal (cl-getf cd :artist) artist))
   pcl/*db*
   ))

(defun pcl/select (selector-fn)
  (cl-remove-if-not selector-fn pcl/*db*)
  )
Same as Seibel's common lisp, except adding prefix cl- to the common lisp names remove-if-not and getf.
In elisp (at least) the #' quote before a lambda expression is optional, so we could just as well use:
(defun pcl/select-by-artist (artist)
  (cl-remove-if-not
   (lambda (cd) (equal (cl-getf cd :artist) artist))
   pcl/*db*
   ))
And likewise for the remaining examples.
(defun pcl/artist-selector (artist)
  #'(lambda (cd) (equal (cl-getf cd :artist) artist))
  )
Note this closure over artist will only work under lexical binding.

Note also that pcl/select-by-artist does not have an interactive declaration, so as is you cannot call it as command.
One way to try out code is to use the ielm emacs command, which provides an elisp REPL (Read-Evaluate-Print-Loop) environment in an emacs buffer.

(cl-defun pcl/where/ifs (&key title artist rating (ripped nil ripped-p))
  #'(lambda (cd)
     (and
      (if title    (equal (cl-getf cd :title)  title)  t)
      (if artist   (equal (cl-getf cd :artist) artist) t)
      (if rating   (equal (cl-getf cd :rating) rating) t)
      (if ripped-p (equal (cl-getf cd :ripped) ripped) t)
      )))
I took the liberty of renaming this version of where to "pcl/where/ifs",
to distinguish it from the much cooler macro based version Seibel defines later in the chapter (see below).

Finally, note the use of cl-defun instead of plain defun, since in elisp
plain defun does not provide for named arguments such as :artist or :title.

Updating Existing Records

(cl-defun pcl/update (selector-fn &key title artist rating (ripped nil ripped-p))
  "Update given fields of entries in database selected by SELECTED-FN function."
  (setq pcl/*db*
        (mapcar
         #'(lambda (row)
            (when (funcall selector-fn row)
              (if title    (setf (cl-getf row :title)  title))   ;; These three if expressions follow a pattern
              (if artist   (setf (cl-getf row :artist) artist))  ;; and could also be generated on the fly
              (if rating   (setf (cl-getf row :rating) rating))  ;; with a macro if desired.
              (if ripped-p (setf (cl-getf row :ripped) ripped))  ;; This one follows a different pattern.
              )
             row
             )
         pcl/*db*
         )))
A minor difference is I happened to use setq instead of setf to set the database variable.
Either work to set variables in either lisp, but I've seen setq more often in elisp.
(defun pcl/delete-rows (selector-fn)
  (setq pcl/*db* (cl-remove-if selector-fn pcl/*db*))
  )

(defun pcl/make-comparison-expr (field value)
  `(equal (cl-getf cd ,field) ,value)
  )
Identical to common lisp.

Removing Duplication and Winning Big

Now we are approaching the crescendo -- Seibel's beautiful WHERE macro.
(defun pcl/make-comparisons-list (clauses)
  "Return list of comparison expressions for the given clauses

CLAUSES is a list of pairs   :field-name  value"
  (cl-loop
   while clauses
   collecting
   (pcl/make-comparison-expr (pop clauses) (pop clauses))
   ))
Essentially identical. I happened to add a docstring (could be done in common lisp as well).

Otherwise the only difference is cl-loop instead of just loop.
An alternative implementation (for either kind of lisp) would be:

(defun pcl/make-comparisons-list (clauses)
  (let (acc)
    (while clauses
      (push
       (pcl/make-comparison-expr (pop clauses) (pop clauses))
       acc
       )
      )
     (reverse acc)   ;; By habit. Reversing not really necessary in this case.
    ))

Looking at the side-by-side (pop clauses) expressions, one can see that the code relies on those expressions being evaluated in order.
If the second one were to be evaluated first, field and value would be reversed!
Fortunately both the common lisp standard (in CLHS section 3.1.2.1.2.3, "Function Forms") and the Elisp info documentation state that function call arguments are evaluated sequentially in order.

(defmacro pcl/where/macro (&rest clauses)
  "SQL WHERE-like function to select records in a property list based database

CLAUSES is a list of pairs   :field-name  value
"
  `#'(lambda (cd) (and ,@(pcl/make-comparisons-list clauses))
       ))
This is the wonderful macro version of the where given and explained by Seibel.
No difference between elisp and common lisp (except I happened to add a docstring here too).

<,@> is the splice operator. Again, see Seibel for an explanation.


(macroexpand-1 '(pcl/where/macro :title "Dark side of the moon" :ripped t))
See what the macro expands to. Both lisps provide macroexpand-1.
To see the where macro in action, evaluate this expression with your desired field names and values.
For example:

(pcl/display-entries
 (pcl/select (pcl/where/macro :rating 7 :ripped t :title "Home"))
 )
A more emacs way would be to define an interactive command for selectively displaying entries, but I leave that as an exercise for the reader.

Code Download

I have put the elisp code presented above in a
source file for download.

Summary

In Chapter 3 of Practical Common Lisp,
Peter Seibel provides a useful small program to help beginners learn common lisp.

In particular he does a fantastic job of motivating the use of lisp macros,
providing an elegant and natural application in the macro version of his where function.

This page gives an elisp version of his program.
Naturally, the elisp version uses buffers to do things the common lisp version would use standard in/out for,
but overall the elisp version is very close to the original common lisp - much of it in fact identical.