实用的通用LISP理解第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. 为什么when表达式后面有一个row,似乎属于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

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的元素,update它们用于设置具有新值的字段。

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

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

最新更新