9

Lisp のバッククォート読み取りマクロに問題があります。逆引用符を埋め込む必要があると思われるマクロを記述しようとすると (たとえば、``(w ,x ,,y)Paul Graham のANSI Common Lispの399 ページから)、コンパイルできるようにコードを記述する方法がわかりません。通常、私のコードは、「バッククォート内にカンマがありません」で始まる一連のエラーを受け取ります。適切に評価されるコードを作成する方法について、誰かがガイドラインを提供できますか?

例として、現在、次の形式でルールを記述し'(function-name column-index value)、述語ラムダ本体を生成して、特定の行の によってインデックス付けされた要素column-indexがルールを満たすかどうかを判断するマクロが必要です。rule を指定してこのマクロを呼び出すと'(< 1 2)、次のようなラムダ本体が生成されます。

(lambda (row)
  (< (svref row 1) 2))

これで私ができる最高の刺し傷は次のとおりです。

(defmacro row-satisfies-rule (rule)
  (let ((x (gensym)))
    `(let ((,x ,rule))
       (lambda (row)
         (`,(car ,x) (svref row `,(cadr ,x)) `,(caddr ,x))))))

評価時に、SBCL は次のエラー レポートを吐き出します。

; in: ROW-SATISFIES-RULE '(< 1 2)
;     ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))
; 
; caught ERROR:
;   illegal function call

;     (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
; ==>
;   #'(LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
; 
; caught STYLE-WARNING:
;   The variable ROW is defined but never used.

;     (LET ((#:G1121 '(< 1 2)))
;       (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))))
; 
; caught STYLE-WARNING:
;   The variable #:G1121 is defined but never used.
; 
; compilation unit finished
;   caught 1 ERROR condition
;   caught 2 STYLE-WARNING conditions
#<FUNCTION (LAMBDA (ROW)) {2497F245}>

必要なコードを生成するマクロを作成するにはどうすればよいですか? 特に、どのように実装すればよいrow-satisfies-ruleですか?


Ivijay と discipulus のアイデアを使用して、マクロを修正して、コンパイルして動作するようにし、フォームを引数として渡すこともできるようにしました。rowコードをよりスムーズにするために引数として含めることを決定したため、最初に計画したマクロとは少し異なって実行されます。しかし、それは罪のように醜いです。への呼び出しなしで同じように実行するように、それをクリーンアップする方法を知っている人はいますevalか?

(defmacro row-satisfies-rule-p (row rule)
  (let ((x (gensym))
        (y (gensym)))
    `(let ((,x ,row)
           (,y ,rule))
       (destructuring-bind (a b c) ,y
         (eval `(,a (svref ,,x ,b) ,c))))))

また、実行時に引数を適切に評価するためのコードを生成するマクロを取得するクリーンで Lispy の方法の説明も大歓迎です。

4

3 に答える 3

12

まず第一に、Lisp マクロには「分解」引数リストがあります。(rule)これは、引数リストを用意してから で分解する代わりに、(car rule) (cadr rule) (caddr rule)単純に引数リストを作成できることを意味する優れた機能です((function-name column-index value))。このように、マクロは引数として 3 つの要素のリストを想定し、リストの各要素は引数リスト内の対応するシンボルにバインドされます。これを使用してもしなくてもかまいませんが、通常はこちらの方が便利です。

Next,`,は実際には何もしません。なぜなら、逆引用符は Lisp に次の式を評価しないように指示し、コンマは最終的にそれを評価するように指示するからです。私はあなたが単に を意味したと思います。,(car x)これは を評価し(car x)ます。構造化引数を使用する場合、これはとにかく問題になりません。

(gensym)また、マクロ展開で新しい変数を導入していないため、この場合は必要ないと思います。

したがって、マクロを次のように書き換えることができます。

(defmacro row-satisfies-rule ((function-name column-index value))
  `(lambda (row)
     (,function-name (svref row ,column-index) ,value)))

これは、あなたが望むように展開します:

(macroexpand-1 '(row-satisfies-rule (< 1 2)))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))

お役に立てれば!


ルール セットを取得するために引数を評価する必要がある場合は、次の方法で実行できます。

(defmacro row-satisfies-rule (rule)
  (destructuring-bind (function-name column-index value) (eval rule)
    `(lambda (row)
       (,function-name (svref row ,column-index) ,value))))

次に例を示します。

(let ((rules '((< 1 2) (> 3 4))))
  (macroexpand-1 '(row-satisfies-rule (car rules))))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))

ちょうど前のように。


rowマクロに含めて、それを行う関数を作成する代わりに、すぐに答えを出してもらいたい場合は、これを試してください。

(defmacro row-satisfies-rule-p (row rule)
  (destructuring-bind (function-name column-index value) rule
    `(,function-name (svref ,row ,column-index) ,value)))

または、引数を評価する必要がある場合(たとえば、 orの代わりにrule渡す)、単に使用します'(< 1 2)(car rules)(< 1 2)(destructuring-bind (function-name column-index value) (eval rule)


実際、あなたがやろうとしていることには、マクロよりも関数の方が適しているようです。単に

(defun row-satisfies-rule-p (row rule)
  (destructuring-bind (function-name column-index value) rule
    (funcall function-name (svref row column-index) value)))

マクロと同じように機能し、バッククォートの混乱を心配することなく、はるかにきれいです。

一般に、関数によって達成できるものにマクロを使用するのは Lisp スタイルとしては良くありません。

于 2011-06-25T00:58:45.447 に答える
6

理解しておくべきことの1つは、バッククォート機能はマクロとはまったく関係がないということです。リストの作成に使用できます。ソースコードは通常リストで構成されているため、マクロで便利な場合があります。

CL-USER 4 > `((+ 1 2) ,(+ 2 3))
((+ 1 2) 5)

バッククォートはクォートされたリストを紹介します。コンマは引用符を外します。コンマが評価され、結果が挿入された後の式です。カンマはバッククォートに属します。コンマはバッククォート式内でのみ有効です。

これは厳密にはLispリーダーの機能であることに注意してください。

上記は基本的に次のようになります。

CL-USER 5 > (list '(+ 1 2) (+ 2 3))
((+ 1 2) 5)

これにより、最初の式(引用符で囲まれているため評価されません)と2番目の式の結果を含む新しいリストが作成されます。

Lispがバッククォート表記を提供するのはなぜですか?

ほとんどの要素が評価されていないが、いくつかは評価されているリストを作成したい場合に、単純なテンプレートメカニズムを提供するためです。さらに、バッククォートされたリストは結果リストに似ています。

于 2011-06-25T16:42:38.013 に答える
4

この問題を解決するためにネストされた逆引用符は必要ありません。また、マクロの場合は、引数を引用符で囲む必要はありません。よりもlispier(row-satisfies-rule (< 1 2))です(row-satisfies-rule '(< 1 2))

(defmacro row-satisfies-rule (rule)
  (destructuring-bind (function-name column-index value) rule
    `(lambda (row)
       (,function-name (svref row ,column-index) ,value))))

最初の形式のすべての呼び出しの問題を解決します。2 番目のフォームで問題を解決することは、演習として残します。

于 2011-06-25T00:26:47.993 に答える