如何在Common Lisp上正确传递参数?

时间:2016-02-02 02:12:36

标签: lisp common-lisp sbcl

我目前正在阅读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的完全初学者,有人能告诉我我做错了什么吗?我很确定我没有正确地将参数传递给函数。

4 个答案:

答案 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*)))