Я смотрю на третью главу в Practical Common Lisp. В этой главе создается такая база данных, как приложение. Я застрял в понимании функции update
.Практическое общее понимание LISP глава 3
Я написал код в моем редакторе и поместить комментарии в течение моего собственного понимания кода:
(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
Я полностью забыл, что есть 'setf' вместо' equal' и думал, что это был тот же самый код. Также я не знал о том, что lambdas может возвращать ценности в общем lisp. Я только догадывался об этом, потому что я не мог найти другую причину существования «строки». Теперь я знаю. Благодаря! – Zelphir
О, еще один вопрос относительно этого кода: Почему я не могу написать '(selector-fn row)' вместо '(funcall selector-fn row)'? – Zelphir
@ Zelphir, в Common Lisp вы можете иметь составные формы типа: '(параметры имени функции)' или '((lambda (args) body)), но не' (параметры функциональной переменной) ', например на Схеме и аналогичных языках. По этой причине, если у вас есть переменная, привязанная к функции, вы должны использовать [funcall] (http: //www.lispworks.com/documentation/HyperSpec/Body/f_funcal.htm # funcall) или [применить] (http://www.lispworks.com/documentation/HyperSpec/Body/f_apply.htm#apply). Для обсуждения этого вопроса см. [Wiki] (https://en.wikipedia.org/wiki/Common_Lisp#The_function_namespace) или google «lisp1 vs lisp2». – Renzo