2017-05-21 7 views
0

私はPeter SeibelのPractical Common LispでMP3データベースの例を調べています。 Seibelは、マクロを使用してwhere関数のコードを短縮する方法を示します。今、私はupdate関数のコードを短縮するためにマクロを使用しようとしています。 (update機能のオリジナルバージョンは、参考のために含まれています。)私は自分のコードを実行すると、以下のエラーが最後から2番目の行から発信 -なぜ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文を抽象化できます。ここで以下updatemake-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

マクロ展開(いわゆる「マクロ展開フェーズ」) - コードの断片がコンパイルまたはロードされる頃に発生。このケースでは、コンパイル/ロードについてはupdateの話です。マクロは、シンボルtermsにバインドされたfieldsで展開されます。このシンボル自体は、make-comparison-listの値として使用されます。あなたが期待していたものではないと思います。あなたが行くと(Emacsの+ SLIMEで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))のようなリストではありません。

実行時に動的であれば、実行時にマクロを大部分のコードに展開する必要があります。したがって、マクロは単なるソースの書き換えサービスに過ぎず、実行時に何らかの変数が存在する可能性はありません。

関連する問題