实用的共同LISP理解第3章

时间:2016-08-11 12:58:40

标签: lambda lisp common-lisp practical-common-lisp

我看了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返回更新的行?
  4. 我觉得这个代码没有正确解释一些相当高级的语法,我只是猜测代码是如何工作的。

    对代码的示例调用将是:

    (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
    

1 个答案:

答案 0 :(得分:4)

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中它们是(funcall f args)用于设置具有新值的字段。

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

  3. when内的匿名函数以这种方式工作:首先,如果满足setf内的条件,则通过row执行赋值来更新行。最后,它会返回selector-fn,如果*db*已返回 true false ,则可以修改data_transformer.cpp:239] Check failed: channels == img_channels (3 vs. 1) 。因此,更新函数使用该匿名函数返回的值更新channels