2016-08-11 2 views
3

Я смотрю на третью главу в 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)))) 

Как вы можете видеть есть несколько вопросов для кода, представленного в книге. Я перечислил их ниже, но оставьте комментарии, чтобы они были понятнее, к чему они относятся.

  1. На первый взгляд уже это похоже на дублирование кода. Почему я не могу как-то использовать функцию where, вместо того, чтобы писать все эти if выражения еще раз?
  2. Если funcall (что не объяснено в книге в этой главе для этого кода ...) действительно вызывает функцию селектора, которая является возвращаемым значением, вызова функции where, то почему я должен написать все эти if выражения там? Разве это не то, что возвращает функция where? Селектор для тех строк, которые соответствуют критериям?
  3. Почему существует 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 

ответ

4

where функция выполняет SQL-подобный «выбор» элементов путем оценки (с and) различные строки:

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

, чтобы найти, если в сертификате серт ain "field" имеет значение, указанное в качестве параметра функции. Так, например, вы можете называть его (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) соответствует Whe 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-fntrue или false. Таким образом, функция обновления обновляет значение *db* со значениями, возвращаемыми этой анонимной функцией.

+0

Я полностью забыл, что есть 'setf' вместо' equal' и думал, что это был тот же самый код. Также я не знал о том, что lambdas может возвращать ценности в общем lisp. Я только догадывался об этом, потому что я не мог найти другую причину существования «строки». Теперь я знаю. Благодаря! – Zelphir

+0

О, еще один вопрос относительно этого кода: Почему я не могу написать '(selector-fn row)' вместо '(funcall selector-fn row)'? – Zelphir

+1

@ 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

Смежные вопросы