3

Practical Common Lispの第 3 章を見ています。その章では、アプリケーションのようなデータベースを作成します。私は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後にあるのはなぜですか? 更新された行を返すように、式は何も返さないため、それは戻り値ですか?whenlambdawhenlambda

このコードの非常に高度な構文が適切に説明されていないように感じ、そのコードがどのように機能するかを推測しています。

コードの呼び出し例は次のとおりです。

(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

この関数は、さまざまな行whereを ( で) 評価することにより、要素の SQL に似た「選択」を実行します。and

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

特定の「フィールド」に、関数のパラメーターとして指定された値があるかどうかを確認します。したがって、たとえば(where :rating 10 :ripped nil)、テキストで説明されているように呼び出すことができます。

このupdate関数は、代わりに 1 つ以上のフィールドの 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部分に対応し、行は部分(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. コードの重複はありません。2 つの行セットは 2 つの異なるタスクを実行します。whereで要素をフィルタリングするためequalupdate使用され、フィールドに新しい値を設定するために使用されます。

  2. (funcall f args)関数fを引数に適用しますargswhereそのため、行ごとにセレクターが呼び出され、変更が必要な行のみをフィルター処理する述語を満たすかどうかが確認されます。

  3. 内部の無名関数updateは次のように機能します。まず、内部の条件whenが満たされた場合、 を介して代入を実行して行を更新しますsetf。最後に、がtrueまたはfalseを返しrowた場合、それは変更可能または変更不可能なを返します。そのため、更新関数は、その無名関数によって返された値で を更新します。selector-fn*db*

于 2016-08-11T13:38:23.783 に答える