【问题标题】:Common Lisp: Passing a lambda generated by a macro to remove-if-not results in errorCommon Lisp:将宏生成的 lambda 传递给 remove-if-not 会导致错误
【发布时间】:2018-12-06 19:26:25
【问题描述】:

当我在本书提供的最终产品中遇到错误时,我正在关注 Practical Common Lisp 书中的第一个示例项目。

该项目是一个存储有关 cd 信息的基本数据库。它支持与 where 宏一起使用的 select 和 update 语句。

我已经使用 clisp 和 sbcl 编译器测试了代码,两者都出现了同样的错误。

代码如下:

(defvar *db*)

(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)
    (with-standard-io-syntax
      (print *db* out))))

(defun load-db (filename)
  (with-open-file (in filename)
    (with-standard-io-syntax
      (setf *db* (read in)))))

(defun select (selector-fn)
  (remove-if-not selector-fn *db*))

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

(defun 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 (cd)
         (when (funcall selector-fn cd)
           (if title (setf (getf cd :title) title))
           (if artist (setf (getf cd :artist) artist))
           (if rating (setf (getf cd :rating) rating))
           (if ripped-p (setf (getf cd :ripped) ripped)))
         cd)
     *db*)))

(defun delete-rows (selector-fn)
  (setf *db* (remove-if selector-fn *db*)))

这是产生错误的调用:

(select (where :artist "Dixie Chicks"))

基本上,这次通话中发生的情况如下。 where 宏给出了一个 lambda 表达式,它接受一个 cd 并作为一个谓词来确定 cd 是否具有某些字段的某些值。

在此特定调用中,where 宏扩展为:

#'(lambda (cd) (and (equal (getf cd :artist) "Dixie Chicks")))

这是 db 当前定义为:

((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 7 :RIPPED T)
 (:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)
 (: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 "Naive" :ARTIST "The Kooks" :RATING 6 :RIPPED T)
 (:TITLE "It's the end of the world as we know it" :ARTIST "REM" :RATING 6
  :RIPPED T)
 (:TITLE "We Walk" :ARTIST "REM" :RATING 8 :RIPPED T))

这是上面调用产生的错误:

The value
  #'(LAMBDA (CD) (AND (EQUAL (GETF CD :ARTIST) "Dixie Chicks")))
is not of type
  (OR FUNCTION SYMBOL)
when binding SB-IMPL::PREDICATE
   [Condition of type TYPE-ERROR]

以下是上述调用的预期结果:

((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 7 :RIPPED T)
 (:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T))

在寻求帮助之前,我花了很多时间查看这段代码,但由于我对 Common Lisp 的了解非常原始,我似乎无法找到罪魁祸首。

提前致谢!

【问题讨论】:

  • where 应该是defmacro,而不是defun(更改后不要忘记重新编译where 的所有用户)。
  • @sds 非常感谢。我发誓我盯着这个看了 2 个小时。哎呀。

标签: common-lisp


【解决方案1】:

你错过了一个关键点:

现在您只需将make-comparison-list 返回的列表包含在AND 和一个匿名函数中,您可以在where 宏本身中执行此操作。使用反引号制作模板,通过插入 make-comparisons-list 的值来填写,这很简单。

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

在这个版本中where 是一个宏,而不是一个函数;即您需要使用defmacro 定义它,而不是defun

【讨论】:

    【解决方案2】:

    问题很容易找到。如果你知道去哪里看。 ;-)

    错误提示#'(LAMBDA (CD) (AND (EQUAL (GETF CD :ARTIST) "Dixie Chicks"))) 不是函数或符号。

    这是什么?这是一个列表。

    CL-USER 9 > '#'(LAMBDA (CD) (AND (EQUAL (GETF CD :ARTIST) "Dixie Chicks")))
    (FUNCTION (LAMBDA (CD) (AND (EQUAL (GETF CD :ARTIST) "Dixie Chicks"))))
    
    CL-USER 10 > (type-of *)
    CONS
    

    cons 不是函数对象或符号。

    那么你需要找到为什么你得到这个列表而不是一个函数对象。然后您可以看到 where 返回列表。如果where 是一个宏,它会将此列表创建为源,然后将其评估为一个函数对象。

    【讨论】:

    • 现在这是一个很好的“教他们钓鱼”的答案!所以需要一个“元答案”复选标记。
    猜你喜欢
    • 2013-07-26
    • 2019-11-03
    • 2017-01-24
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    • 2018-09-06
    • 2020-09-18
    • 1970-01-01
    相关资源
    最近更新 更多