为什么 lisp 说这个参数不是列表?
Why is lisp saying this parameter is not a list?
我正在研究 Peter Seibel 的 Practical Common Lisp 中的 MP3 数据库示例。 Seibel 演示了如何使用宏来缩短 where
函数的代码;所以现在,我正在尝试使用宏来缩短 update
函数的代码。 (收录update
函数的原始版本以供参考。)当我运行我的代码时,以下错误来自倒数第二行--
*** - CAR: TERMS is not a list
我做错了什么?这是我的代码。
(defvar *db* nil)
(defun add-record (cd)
(push cd *db*))
(defun dump-db ()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
(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-comparison-list (func fields)
(loop while fields
collecting (funcall func (pop fields) (pop fields))))
(defmacro where (&rest clauses)
`#'(lambda (cd) (and ,@(make-comparison-list 'make-comparison-expr clauses))))
(defun make-update-expr (field value)
`(setf (getf row ,field) ,value))
(defmacro make-update-list (fields)
(make-comparison-list 'make-update-expr fields))
(defun update (selector-fn &rest terms)
(print (type-of terms))
(setf *db*
(mapcar
#'(lambda (row)
(when (funcall selector-fn row)
(make-update-list terms))
row)
*db*)))
;(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 delete-rows (selector-fn)
(setf *db* (remove-if selector-fn *db*)))
;(loop (print (eval (read))))
(add-record (make-cd "Be" "Common" 9 nil))
(add-record (make-cd "Like Water for Chocolate" "Common" 9 nil))
(add-record (make-cd "Be" "Beatles" 9 nil))
(dump-db)
(update (where :artist "Common" :title "Be") :rating 8)
(dump-db)
-----编辑-----
我明白了。解决方案是将 update
设为宏,将 make-update-list
设为函数。这样,make-update-list
可以在 运行 时评估字段,并且 update
仍然可以抽象掉一些乏味的 if 语句。下面是更新后的 update
和 make-update-list
:
(defun make-update-list (fields)
(make-comparison-list 'make-update-expr fields))
(defmacro update (selector-fn &rest terms)
`(setf *db*
(mapcar
#'(lambda (row)
(when (funcall ,selector-fn row)
,@(make-update-list terms))
row)
*db*)))
make-update-list
的宏扩展是在一个单独的阶段(称为 "macroexpansion phase")完成的 - 这发生在一段代码被编译或加载的时候;在这种情况下,我们谈论的是 update
的编译/加载。宏被扩展为 fields
绑定到 符号 terms
,它(符号本身)用作 make-comparison-list
中的值;我想这不是你所期望的。
请注意,如果您逐行编译文件(C-c C-c
在 Emacs + SLIME 中),它会在 编译 期间告诉您正确的update
宏扩展失败,因为 "the value TERMS is not of type LIST"。
通常,将宏视为接受其参数的函数 未计算 - 即表单 (make-update-list foo)
将使用宏参数的 fields
值进行扩展绑定到 foo
。您在这里想要实现的目标 - 基于 运行 时间值的代码生成 - 有点难做。
您正在尝试获取符号的car
!
> (car 'terms)
*** - CAR: TERMS is not a list
将宏视为一种函数,在使用时,它会用宏函数在任何地方使用的结果替换 代码。此时变量只是符号,除此之外没有任何意义。
当你执行 (make-update-list terms)
时,它会调用宏函数,参数 fields
是你传递的符号,即 terms
。由于它是一个符号,因此不能像您尝试的那样对其进行迭代。当它确实是一个列表时,您可以在运行时对其进行迭代,但作为一个宏,它不是一个列表,直到您向它传递一个像 (make-update-list (title artist rating ripped))
这样的列表。
如果它在运行时是动态的,那么您的宏需要扩展为在运行时发挥其大部分魔力的代码。因此宏只是一个源代码重写服务,不应该与运行时的变量有任何关系,因为它已经完成了它的事情。
我正在研究 Peter Seibel 的 Practical Common Lisp 中的 MP3 数据库示例。 Seibel 演示了如何使用宏来缩短 where
函数的代码;所以现在,我正在尝试使用宏来缩短 update
函数的代码。 (收录update
函数的原始版本以供参考。)当我运行我的代码时,以下错误来自倒数第二行--
*** - CAR: TERMS is not a list
我做错了什么?这是我的代码。
(defvar *db* nil)
(defun add-record (cd)
(push cd *db*))
(defun dump-db ()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
(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-comparison-list (func fields)
(loop while fields
collecting (funcall func (pop fields) (pop fields))))
(defmacro where (&rest clauses)
`#'(lambda (cd) (and ,@(make-comparison-list 'make-comparison-expr clauses))))
(defun make-update-expr (field value)
`(setf (getf row ,field) ,value))
(defmacro make-update-list (fields)
(make-comparison-list 'make-update-expr fields))
(defun update (selector-fn &rest terms)
(print (type-of terms))
(setf *db*
(mapcar
#'(lambda (row)
(when (funcall selector-fn row)
(make-update-list terms))
row)
*db*)))
;(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 delete-rows (selector-fn)
(setf *db* (remove-if selector-fn *db*)))
;(loop (print (eval (read))))
(add-record (make-cd "Be" "Common" 9 nil))
(add-record (make-cd "Like Water for Chocolate" "Common" 9 nil))
(add-record (make-cd "Be" "Beatles" 9 nil))
(dump-db)
(update (where :artist "Common" :title "Be") :rating 8)
(dump-db)
-----编辑-----
我明白了。解决方案是将 update
设为宏,将 make-update-list
设为函数。这样,make-update-list
可以在 运行 时评估字段,并且 update
仍然可以抽象掉一些乏味的 if 语句。下面是更新后的 update
和 make-update-list
:
(defun make-update-list (fields)
(make-comparison-list 'make-update-expr fields))
(defmacro update (selector-fn &rest terms)
`(setf *db*
(mapcar
#'(lambda (row)
(when (funcall ,selector-fn row)
,@(make-update-list terms))
row)
*db*)))
make-update-list
的宏扩展是在一个单独的阶段(称为 "macroexpansion phase")完成的 - 这发生在一段代码被编译或加载的时候;在这种情况下,我们谈论的是 update
的编译/加载。宏被扩展为 fields
绑定到 符号 terms
,它(符号本身)用作 make-comparison-list
中的值;我想这不是你所期望的。
请注意,如果您逐行编译文件(C-c C-c
在 Emacs + SLIME 中),它会在 编译 期间告诉您正确的update
宏扩展失败,因为 "the value TERMS is not of type LIST"。
通常,将宏视为接受其参数的函数 未计算 - 即表单 (make-update-list foo)
将使用宏参数的 fields
值进行扩展绑定到 foo
。您在这里想要实现的目标 - 基于 运行 时间值的代码生成 - 有点难做。
您正在尝试获取符号的car
!
> (car 'terms)
*** - CAR: TERMS is not a list
将宏视为一种函数,在使用时,它会用宏函数在任何地方使用的结果替换 代码。此时变量只是符号,除此之外没有任何意义。
当你执行 (make-update-list terms)
时,它会调用宏函数,参数 fields
是你传递的符号,即 terms
。由于它是一个符号,因此不能像您尝试的那样对其进行迭代。当它确实是一个列表时,您可以在运行时对其进行迭代,但作为一个宏,它不是一个列表,直到您向它传递一个像 (make-update-list (title artist rating ripped))
这样的列表。
如果它在运行时是动态的,那么您的宏需要扩展为在运行时发挥其大部分魔力的代码。因此宏只是一个源代码重写服务,不应该与运行时的变量有任何关系,因为它已经完成了它的事情。