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(该代码在那一章的书中没有解释......)真的调用了选择器函数,这是对where给定函数的调用的返回值,那么为什么我必须在if那里写所有这些表达式? 这不正是where函数返回的内容吗?符合条件的那些行的选择器?
  3. row为什么在表达式之后有一个when看似属于lambda表达式的?那是返回值吗,因为when表达式不返回任何内容,因此lambda返回更新的行?

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

对代码的示例调用是:

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

我试过了,它确实有效。

这是我的“数据库”:

((: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
4

1 回答 1

4

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

(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)对应于 wheWHERE部分,而 rows(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它们用于过滤带有 的元素equalupdate它们用于设置具有新值的字段。

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

  3. 内部的匿名函数update以这种方式工作:首先,如果满足内部的条件,则when通过执行分配来更新行setf。最后,它返回,如果已返回truefalserow,则可以修改或不修改。因此,更新函数使用该匿名函数返回的值更新 。selector-fn*db*

于 2016-08-11T13:38:23.783 回答