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))))
ご覧のとおり、この本で紹介されているコードにはいくつかの質問があります。以下にそれらをもう一度リストしますが、それらが何に関連しているかがより明確になるように、コメントを残しておいてください.
- 一見すると、これはコードの重複のように見えます。
where
これらすべてのif
式をもう一度書く代わりに、どうにかして関数を使用できないのはなぜですか? - (その
funcall
コードのその章の本では説明されていません...)実際に、指定された関数への呼び出しの戻り値であるセレクター関数を呼び出す場合、where
なぜそれらすべてのif
式をそこに書かなければならないのですか? それはまさにwhere
関数が返すものではありませんか? 基準に適合する行のセレクターは? - 表現に属しているように見える表現の
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