我目前正在阅读Practical Common Lisp,我决定尝试改进它的第一个例子(simple database),只是为了练习。
我试图让update
函数更通用,比如select
函数。
这是我的代码,它运行但实际上并没有像它应该更新任何内容。
(defun make-if-expr (field value)
`(if ,value (setf (getf row ,field) ,value)))
(defun make-if-list (fields)
(loop while fields
collecting (make-if-expr (pop fields) (pop fields))))
(defun update (selector-fn &rest clauses)
(setf *db*
(mapcar
#'(lambda (row)
(when (funcall selector-fn row)
(apply #'make-if-list clauses))
row)
*db*)))
这是从原始源代码修改的,可在链接中找到。
例如,使用((:TITLE "Title" :ARTIST "Artist" :RATING 7 :RIPPED T))
作为数据库,在REPL中输入(update (where :title "Title") (list :title "New Title"))
应该将标题更改为" New Title"。但事实并非如此。
没有错误,没有任何错误。它只是没有用。
由于我是Common Lisp的完全初学者,有人能告诉我我做错了什么吗?我很确定我没有正确地将参数传递给函数。
答案 0 :(得分:2)
MAKE-IF-LIST
创建一个列表列表并返回它。你没有对它们做任何事情,因此没有任何反应。
还不清楚为什么要创建列表,因为你可以编写一个执行更新的函数。
答案 1 :(得分:1)
我想你试图让update
类似于本章末尾用宏实现where
的方式。在这种情况下,您所犯的错误仅仅是您的update
不是宏。您需要将其更改为一个,并使用反引号语法来评估必要的部分(selector-fn
和(make-if-list ...)
):
(defmacro update (selector-fn &rest clauses)
`(setf *db*
(mapcar
#'(lambda (row)
(when (funcall ,selector-fn row)
,@(make-if-list clauses))
row)
*db*)))
(setf *db* (list (list :TITLE "Title" :ARTIST "Artist" :RATING 7 :RIPPED T)))
(update (where :title "Title") :title "New Title")
*db*
; => ((:TITLE "New Title" :ARTIST "Artist" :RATING 7 :RIPPED T))
编辑:正如Rainer Joswig在评论中所说,上面的代码确实存在一些问题。实际上,您必须确保不要多次评估变量,并且不应该使用周围代码可能使用的符号。这本书应该在后面的章节中介绍,但更好的,虽然有点复杂,代码的版本看起来像这样(另见coredumps回答,因为我不想把它偷到这里):
;; You need to pass the `row-sym` here, because this can't
;; rely on the row being called `row` (which it isn't).
;; You'll also want to see the answer by coredump regarding this.
(defun make-if-expr (field value row-sym)
`(if ,value (setf (getf ,row-sym ,field) ,value)))
(defun make-if-list (fields row-sym)
(loop while fields
collecting (make-if-expr (pop fields) (pop fields) row-sym)))
(defmacro update (selector-fn &rest clauses)
;; You need to use `gensym` to generate unique names
;; for all symbols you use in the expanded code.
(let ((selector-sym (gensym "selector-fn"))
(row-sym (gensym "row")))
;; Assign `selector-fn` into a variable. This avoids the
;; function being built by the `where` macro multiple times.
`(let ((,selector-sym ,selector-fn))
(setf *db*
(mapcar
;; You have to use the unique symbols generated above,
;; so the `*-sym` variables are evaluated with a comma.
#'(lambda (,row-sym)
(when (funcall ,selector-sym ,row-sym)
,@(make-if-list clauses row-sym))
,row-sym)
*db*)))))
答案 2 :(得分:1)
这与您的问题没有直接关系,但是:
(defun make-if-expr (field value)
`(if ,value (setf (getf row ,field) ,value)))
...在两个不同的地方注入value
,这意味着当非零时,它将被评估两次。如果计算值需要时间或者是否会产生副作用,则会出现问题。您应该引入一个变量来保存该值:
(defun make-if-expr (field value)
(let ((symbol (gensym)))
`(let ((,symbol ,value))
(if ,symbol (setf (getf row ,field) ,symbol)))))
答案 3 :(得分:1)
感谢大家给我的精彩提示,我能够解决我的问题。
我试图在if
函数中生成update
s,这只有使用宏才有意义。相反,我现在使用了一个函数。
这是我的新代码,希望它对有类似问题的人有用:
(defun update-if (field value row)
(setf (getf row field) value))
(defun update-if-list (fields row)
(loop while fields
do (update-if (pop fields) (pop fields) row)))
(defun update (selector-fn &rest clauses)
(setf *db*
(mapcar
#'(lambda (row)
(when (funcall selector-fn row)
(update-if-list clauses row))
row)
*db*)))