marcotama
marcotama

Reputation: 2052

How do I fix my first Lisp macro?

I am learning Lisp this week, and I am following the excellent Practical Common Lisp book. The second chapter shows how to create a simple CD database, first the naive way and then removes code repetition with macros. Specifically, this:

(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))))

becomes this:

(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) (and ,@(make-comparisons-list clauses))))

Now I was trying to also convert another of the functions the same way:

(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*)))

which I wrote as:

(defun make-assignment-expr (field value)
  `(setf (getf row ,field) ,value))

(defun make-assignments-list (fields)
  (loop while fields
     collecting (make-assignment-expr (pop fields) (pop fields))))

(defmacro update (selector-fn &rest assignments)
  (setf *db*
    (mapcar
     `#'(lambda (row)
          (when (funcall selector-fn row)
        ,@(make-assignments-list assignments))
          row)
     *db*)))

However, this returns the following warning (though it fails compilation) (and it shows it twice) when I compile it:

warning: 
    Derived type of (LIST #:G20 #:G21) is
    (VALUES CONS &OPTIONAL),
    conflicting with its asserted type
    (OR FUNCTION SYMBOL).
    See also:
    SBCL Manual, Handling of Types [:node]
    --> LET 
    ==>
    (SB-KERNEL:%COERCE-CALLABLE-TO-FUN
    `#'(LAMBDA (ROW)
            (WHEN (FUNCALL SELECTOR-FN ROW) ,@(MAKE-ASSIGNMENTS-LIST ASSIGNMENTS))
            ROW))

Can anyone explain what the problem is and how to fix it? Thanks in advance.

Upvotes: 0

Views: 73

Answers (1)

coredump
coredump

Reputation: 38809

Error message

Derived type of (LIST #:G20 #:G21) is
(VALUES CONS &OPTIONAL),
conflicting with its asserted type
(OR FUNCTION SYMBOL).

You are giving a value of type CONS; the (values ... &optional) bit is a way of saying that there is only one value, not multiple ones. However, you are calling a function which expects either a function object, or a symbol. The incriminated call is the following one:

(SB-KERNEL:%COERCE-CALLABLE-TO-FUN
`#'(LAMBDA (ROW)
        (WHEN (FUNCALL SELECTOR-FN ROW) ,@(MAKE-ASSIGNMENTS-LIST ASSIGNMENTS))
        ROW))

You could try to see where it comes from by macroexpanding your code, but here you already recognize your lambda form and apparently the value is not what you expect it to be.

You are quoting #'(lambda (...)), which denotes (function (lambda (...))). Since you are quoting it, you obtain the list which has the function symbol as a first element, followed by a sublist, etc. So here is the origin of the reported error. By the way, #'(lambda ()) is useless, you can write (lambda ()) directly, but the problem would be the same if you quoted that.

Evaluating code during macro-expansion

The purpose of macros is to transform code; sometimes you may want to modify the compilation environment, but this is not the case here. When I look at your macro, the fact that you are calling setf without quoting it is a major red flag:

(defmacro update (selector-fn &rest assignments)
  (setf *db*
    (mapcar
     `#'(lambda (row)
          (when (funcall selector-fn row)
            ,@(make-assignments-list assignments))
          row)
     *db*)))

You should probably move the backquote around setf and unquote selector-fn.

Upvotes: 3

Related Questions