6

私は Practical Common Lisp を読んだり作業したりしています。私は、Lisp でのテスト フレームワークの構築に関する章にいます。

以下のように機能「test-+」を実装しましたが、動作します:

(defun test-+ ()
  (check
    (= (+ 1 2) 3)
    (= (+ 5 6) 11)
    (= (+ -1 -6) -7)))

覚えておいてください、私は言った、それは機能します.

「test-+」が参照するコードは次のとおりです。

(defmacro check (&body forms)
  `(combine-results
    ,@(loop for f in forms collect `(report-result ,f ',f))))

(defmacro combine-results (&body forms)
  (with-gensyms (result)
    `(let ((,result t))
       ,@(loop for f in forms collect `(unless ,f (setf ,result nil)))
       ,result)))

(defmacro with-gensyms ((&rest names) &body body)
  `(let ,(loop for n in names collect `(,n (gensym)))
     ,@body))

(defun report-result (value form)
  (format t "~:[FAIL~;pass~] ... ~a~%" value form)
  value)

今、私が行っていることは、Slime を使用して、これらを段階的にマクロ展開することです (マクロ展開 1 にマップされている ctrl-c RET を使用)。

したがって、「test-+」の「check」呼び出しは次のように展開されます。

(COMBINE-RESULTS
  (REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3))
  (REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11))
  (REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7)))

そして、これをマクロ展開すると、次のようになります。

(LET ((#:G2867 T))
  (UNLESS (REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3)) (SETF #:G2867 NIL))
  (UNLESS (REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11)) (SETF #:G2867 NIL))
  (UNLESS (REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7))
    (SETF #:G2867 NIL))
  #:G2867)

そして、この文の真上にあるそのコードが機能しません。これを REPL に貼り付けると、次のエラーが表示されます (Clozure Common Lisp を使用しています)。

アンバウンド変数: #:G2867 [タイプ UNBOUND-VARIABLE の条件]

同じコードを使用して、gensym を "x" などの変数名に置き換えると、問題なく動作します。

では、次の驚きをどのように説明できますか。

  1. これらすべてを呼び出す「test-+」マクロは正常に動作します。

  2. 「combine-results」マクロのマクロ展開は実行されませ

  3. 「combine-results」のマクロ展開からgensymを削除すると、機能 ます。

私が推測できる唯一のことは、gensyms の文字通りの使用を含むコードを使用できないということです。もしそうなら、それはなぜですか、そしてそれをどのように回避しますか? そして、それが説明でないなら、それは何ですか?

ありがとう。

4

2 に答える 2

13

GENSYM非インターン シンボルを作成します。マクロが正常に実行される場合、これは問題ではありません。インターンされていない同じシンボルが式全体で置換されているからです。

しかし、式をコピーして REPL に貼り付けると、これは起こりません。#:インターンされていないシンボルを返すようにリーダーに指示します。その結果、出現するたび#:G2867異なるシンボルになり、バインドされていない変数の警告が表示されます。

(setq *print-circle* t)MACROEXPAND を実行する前に実行すると、同じ記号をリンクするために記法が使用されます#n=#n#

于 2012-09-30T00:37:31.477 に答える
11

印刷されて読み戻されたコードは、もはや同じコードではありません。特に、#:G2867印刷された表現の の 2 つのインスタンスは、(同じ名前を共有していても) 2 つの別個のシンボルとして読み戻されますが、元の内部表現では同じである必要があります。

マクロ展開されたコードの印刷表現で ID を保持するには、 に設定*PRINT-CIRCLE*してみてください。T

于 2012-09-30T00:37:09.147 に答える