实用Common LISP理解第3章

Practical Common LISP understanding chapter 3

我在看Practical Common Lisp的第三章。在那一章中,创建了一个类似数据库的应用程序。我无法理解 update 函数。

我已经在我的编辑器中编写了代码并添加了注释以供我自己理解代码:

(defun update (selector-fn &key title artist rating (ripped NIL ripped-p))
    (setf  ; set ...
        *db*  ; *DB* to ...
        (mapcar  ; the value of the application of ...
            #'(lambda (row)  ; a lambda to rows ...
                (when (funcall selector-fn row)  ; when a row satisfies ...
                    ; (what does funcall do? if I call the selector function
                    ; why do I still have to check for predicates as below?)
                    ; maybe "when a row is selected"? Is WHEN like a loop over rows?
                    (if title (setf (getf row :title) title))  ; the title predicate ...
                    (if artist (setf (getf row :artist) artist))  ; the artist predicate ...
                    (if rating (setf (getf row :rating) rating))  ; the rating predicate ...
                    (if ripped-p (setf (getf row :ripped) ripped)))  ; and the ripped predicate ...
                ; why cannot we use our selector function here instead of repeating stuff?!
                row)  ; why is there a ROW here? isn't the lambda expression already finished?
                ; maybe the WHEN expression does not return anything and this is a return value of the lambda?
            *db*)))  ; applies the lambda to the database

之前给出了一个where函数:

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

如您所见,本书中的代码存在一些问题。我会在下面再次列出它们,但请留下评论,以便更清楚地了解它们的相关内容。

  1. 乍一看,这看起来像是代码重复。为什么我不能以某种方式使用 where 函数,而不是再次编写所有这些 if 表达式?
  2. 如果funcall(书中那一章没有解释那个代码...)真的调用了选择器函数,也就是调用的return值where 函数给定了,那我为什么还要写那些 if 表达式呢?这不正是 where 函数 return 的作用吗?那些符合条件的行的选择器?
  3. 为什么when后面有个row,貌似是属于lambda的?这是一个 return 值吗,因为 when 表达式没有 return 任何东西,所以 lambda return 是更新的行?

我觉得这段代码没有正确解释一些非常高级的语法,我只能猜测这段代码究竟是如何工作的。

代码调用示例如下:

(update (where :artist "artist1") :rating 11)

我试过了,确实有效。

这是我的 "database":

((:TITLE "title3" :ARTIST "artist1" :RATING 10 :RIPPED T)
(:TITLE "title2" :ARTIST "artist2" :RATING 9 :RIPPED T)
(:TITLE "title1" :ARTIST "artist1" :RATING 8 :RIPPED T))

这是目前为止的完整代码:

(getf (list :a 1 :b 2 :c 3) :b)

(defvar *db* nil)

(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)  ; this is a list as parameter! not a function call
        ; OUT holds the output stream
        ; opening a file for writing with :DIRECTION :OUTPUT
        ; if it already exists overrite it :IF-EXISTS :SUPERSEDE
        (with-standard-io-syntax (print *db* out))
        ; The macro WITH-STANDARD-IO-SYNTAX ensures that certain variables
        ; that affect the behavior of PRINT are set to their standard values.
    ))

(defun load-db (filename)
    (with-open-file
        (in filename)
        ; IN contains the input stream
        (with-standard-io-syntax (setf *db* (read in)))
        ; file contains standard syntax of lisp
        ; SETF sets the value of *DB* to what is read from IN
        ; WITH-STANDARD-IO-SYNTAX macro ensures that READ is using the same basic
        ; syntax that save-db did when it PRINTed the data.
    ))

(defun select-by-artist (artist)
    (remove-if-not
        #'(lambda (cd) (equal (getf cd :artist) artist))
        *db*))

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

(load-db "database")
(dump-db)

; not so general selector function
(defun artist-selector (artist)
    #'(lambda (cd) (equal (getf cd :artist) artist)))

; "general" selector function
(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))))

(print (select (where :artist "artist1")))

(defun update (selector-fn &key title artist rating (ripped NIL ripped-p))
    (setf  ; set ...
        *db*  ; *DB* to ...
        (mapcar  ; the value of the application of ...
            #'(lambda (row)  ; a lambda to rows ...
                (when (funcall selector-fn row)  ; when a row satisfies ...
                    ; (what does funcall do? if I call the selector function
                    ; why do I still have to check for predicates as below?)
                    ; maybe "when a row is selected"? Is WHEN like a loop over rows?
                    (if title (setf (getf row :title) title))  ; the title predicate ...
                    (if artist (setf (getf row :artist) artist))  ; the artist predicate ...
                    (if rating (setf (getf row :rating) rating))  ; the rating predicate ...
                    (if ripped-p (setf (getf row :ripped) ripped)))  ; and the ripped predicate ...
                ; why cannot we use our selector function here instead of repeating stuff?!
                row)  ; why is there a ROW here? isn't the lambda expression already finished?
                ; maybe the WHEN expression does not return anything and this is a return value of the lambda?
            *db*)))  ; applies the lambda to the database

where 函数通过评估(使用 and)不同的行来执行类似 SQL 的元素“选择”:

(if title (equal (getf cd :title) title) T)
...

查找某个“字段”是否具有指定为函数参数的值。因此,例如,您可以按照文本中的说明使用 (where :rating 10 :ripped nil) 来调用它。

update 函数改为对一个或多个字段执行类似于 SQL 的“更新”。你应该注意到匿名内部函数的主体与 where 函数完全不同,因为它有这样的行:

(if title (setf (getf row :title) title))
...

这些是更新“字段”所需的行,而不是测试它们。事实上他们使用 setf,而不是 equal。因此,如果我们用通用的 SQL 查询进行并行处理,则 when 函数对应于 SQL WHERE:

之后的部分
SELECT *
FROM CDs
WHERE field1 = value1
  AND field2 = value2,
      ...

而在 update 函数中,对 (funcall selector-fn row) 的调用对应于 WHERE 部分,而行 (if ... (setf ...)) 对应于 SET部分:

UPDATE CDs
SET field1 = value1,
    field2 = value2,
    ...
WHERE field1 = value1
  AND field2 = value2,
      ...

例如,这样的调用:

(update (where :artist "artist1") :rating 11)

等同于 SQL 查询:

UPDATE CDs
SET rating = 11
WHERE artist = 'artist1'

所以,你们评论里面update里面的东西叫; the title predicate等等,真的应该是; the setting of the title,等等

那么,您的问题的答案是:

  1. 没有代码重复,两组行执行两个不同的任务:在where中它们用于过滤具有equal的元素,在[=19中=] 它们用于为字段设置新值。

  2. (funcall f args) 将函数 f 应用于参数 args。因此,选择器 where 会为每一行调用,以查看它是否满足谓词 ,该谓词仅过滤必须修改的行

  3. update中的匿名函数是这样工作的:首先,如果满足when中的条件,更新行,通过[=执行赋值21=]。最后,returnsrow,如果selector-fn返回truefalse[,可以修改或不修改=78=]。因此,更新函数使用该匿名函数返回的值更新 *db*