2017-05-21 1 views
0

Peter Seibel의 Practical Common Lisp의 MP3 데이터베이스 예제를 통해 작업하고 있습니다. Seibel은 매크로를 사용하여 where 함수의 코드를 단축하는 방법을 보여줍니다. 이제는 매크로를 사용하여 update 함수의 코드를 줄이려고합니다. 합니다 (update 함수의 원래 버전은 참조를 위해 포함되어 있습니다.) 내 코드를 실행하면 다음과 같은 오류가 마지막에서 두 번째 줄에서 유래 -왜 lisp가이 매개 변수가 목록이 아니라고 말하고 있습니까?

*** - CAR: TERMS is not a list 

내가 잘못 뭐하는 거지? 여기 내 코드가있다.

(defvar *db* nil) 
(defun add-record (cd) 
    (push cd *db*)) 

(defun dump-db() 
    (dolist (cd *db*) 
    (format t "~{~a:~10t~a~%~}~%" cd))) 

(defun make-cd (title artist rating ripped) 
    (list :title title :artist artist :rating rating :ripped ripped)) 

(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) 
        (with-standard-io-syntax 
        (print *db* out)))) 

(defun load-db (filename) 
    (with-open-file (in filename) 
        (with-standard-io-syntax 
        (setf *db* (read in))))) 

(defun select (selector-fn) 
    (remove-if-not selector-fn *db*)) 

(defun make-comparison-expr (field value) 
    `(equal (getf cd ,field) ,value)) 

(defun make-comparison-list (func fields) 
    (loop while fields 
     collecting (funcall func (pop fields) (pop fields)))) 

(defmacro where (&rest clauses) 
    `#'(lambda (cd) (and ,@(make-comparison-list 'make-comparison-expr clauses)))) 

(defun make-update-expr (field value) 
    `(setf (getf row ,field) ,value)) 

(defmacro make-update-list (fields) 
    (make-comparison-list 'make-update-expr fields)) 

(defun update (selector-fn &rest terms) 
    (print (type-of terms)) 
    (setf *db* 
     (mapcar 
      #'(lambda (row) 
      (when (funcall selector-fn row) 
       (make-update-list terms)) 
      row) 
      *db*))) 

;(defun update (selector-fn &key title artist rating (ripped nil ripped-p)) 
; (setf *db* 
;  (mapcar 
;   #'(lambda (row) 
;    (when (funcall selector-fn row) 
;    (if title (setf (getf row :title) title)) 
;    (if artist (setf (getf row :artist) artist)) 
;    (if rating (setf (getf row :rating) rating)) 
;    (if ripped-p (setf (getf row :ripped) ripped))) 
;    row) 
;   *db*))) 

(defun delete-rows (selector-fn) 
    (setf *db* (remove-if selector-fn *db*))) 

;(loop (print (eval (read)))) 
(add-record (make-cd "Be" "Common" 9 nil)) 
(add-record (make-cd "Like Water for Chocolate" "Common" 9 nil)) 
(add-record (make-cd "Be" "Beatles" 9 nil)) 

(dump-db) 
(update (where :artist "Common" :title "Be") :rating 8) 
(dump-db) 

----- 편집 -----

내가 그것을 알아 냈다. 해결책은 update 매크로를 만들고 make-update-list을 기능으로 만드는 것이 었습니다. 이렇게하면 make-update-list은 런타임에 필드를 평가할 수 있고 update은 지루한 if 문을 추상화 할 수 있습니다. 여기서 아래 update 및 갱신된다 make-update-list : 별도의 단계에서 수행되는 make-update-list

(defun make-update-list (fields) 
    (make-comparison-list 'make-update-expr fields)) 

(defmacro update (selector-fn &rest terms) 
    `(setf *db* 
     (mapcar 
      #'(lambda (row) 
      (when (funcall ,selector-fn row) 
       ,@(make-update-list terms)) 
      row) 
      *db*))) 

답변

4

Macroexpansion (소위 "macroexpansion 단계") - 코드 부분은 컴파일되거나로드되는시기에 발생; 여기서는 컴파일/로딩 update에 대해 이야기하고 있습니다. 매크로는 fields으로 확장되어 기호terms으로 연결되며 기호 (기호 자체)는 make-comparison-list의 값으로 사용됩니다. 그게 네가 기대했던 것이 아니 겠지. 당신이 가서 (이맥스 + 점액에서 C-c C-c) 파일 라인별로 컴파일하면 값 조건이 아니다 "때문에

주, 그것은 매크로 확장이 실패 할 권리 update컴파일 동안 말씀 드리죠 LIST 유형 ".

은 일반적으로 자신의 주장에 걸릴 기능 평가되지 않은으로 매크로 생각 - 즉 (make-update-list foo)foo에 바인딩 매크로 변수의 fields 값으로 확장 얻을 것이다 형태. 여기서 달성하고자하는 것은 런타임 값을 기반으로 한 코드 생성입니다.

+0

나는 그 마지막 문장을 약간 확장 할 수 있기를 바랬다. 불가능하거나 어렵게 할 수 있습니까? 컴파일 언어와 런타임 사이에는 일반적으로 어려운 경계가 있기 때문에 불가능하다고 생각합니다. 아니면 방법이 있습니까? – barinska

+1

좀 더 성가시다. 런타임에 빌드 한 s- 표현식을 평가하기 위해'eval'을 사용하려고합니다. 'eval'도 매크로 확장 단계를 수행합니다 ('macroexpand-1'처럼). 자세한 내용은 http://clhs.lisp.se/Body/f_eval.htm을 참조하십시오. 나는 나의 코멘트의 마지막 부분을 의도적으로 확장하지 않았다. 왜냐하면 약간의 경고가있을 수 있기 때문이다. 런타임에 매크로를 제대로 설명하기에 충분하지 않습니다. 즉, 런타임에 수행중인 작업을 이해하는 것은 의미가 없습니다. 코드 생성의 오버 헤드는 간단한 'if'를 건너 뛰는 것보다 큽니다. – TeMPOraL

2

기호로 car을 사용하려고합니다! 사용할 때, 사방이 사용되는 매크로 함수의 결과로 코드를 대체하는 함수로 매크로

> (car 'terms) 
*** - CAR: TERMS is not a list 

생각합니다. 이 시점에서 변수는 단지 기호 일 뿐이며 그 외에 의미가 없습니다.

(make-update-list terms)을 입력하면 fields이라는 매크로 함수가 terms 인 전달 된 기호가됩니다. 심볼이기 때문에 당신이 시도하는 것처럼 반복 될 수 없습니다. 런타임시 반드시 목록 일 때 반복 할 수 있지만 매크로로는 (make-update-list (title artist rating ripped))과 같은 목록을 전달할 때까지 목록이 아닙니다.

런타임에 동적 인 경우 매크로는 런타임시 대부분의 마법을 수행하는 코드로 확장해야합니다. 따라서 매크로는 소스 재 작성 서비스 일 뿐이므로 실행 시간에 어떤 변수가 있을지와 관계가 없어야합니다.

관련 문제