如何修复我的第一个Lisp宏?

时间:2017-10-29 00:49:04

标签: macros lisp

本周我正在学习Lisp,我正在阅读优秀的Practical Common Lisp书。第二章介绍如何创建一个简单的CD数据库,首先是天真的方式,然后用宏删除代码重复。具体来说,这个:

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

成为这个:

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

现在我试图以同样的方式转换另一个函数:

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

我写的:

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

但是,当我编译它时,它会返回以下警告(虽然编译失败)(并且它显示了两次):

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

任何人都可以解释问题是什么以及如何解决它? 提前谢谢。

1 个答案:

答案 0 :(得分:3)

错误消息

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

您提供的值为CONS; (values ... &optional)位是一种说法,只有一个值,而不是多个值。但是,您正在调用一个函数,该函数需要函数对象或符号。有罪的电话是以下一个:

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

您可以尝试通过宏扩展代码来查看它的来源,但是在这里您已经识别出您的lambda形式,并且显然该值不是您期望的值。

您引用#'(lambda (...)),表示(function (lambda (...)))。 由于您正在引用它,因此您将获得列表,其中function符号作为第一个元素,后跟子列表等。因此,这是报告错误的来源。顺便说一下,#'(lambda ())没用,你可以直接写(lambda ()),但如果你引用它,问题就会一样。

在宏扩展期间评估代码

宏的目的是转换代码;有时您可能想要修改编译环境,但这不是这种情况。当我查看您的宏时,您在不引用它的情况下调用setf这一事实是一个重大的危险信号:

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

您应该将反引号移到setf并取消引用selector-fn