3

此宏的目的是创建一个宏,该宏为访问关联列表的某个键提供一个名称。

(defmacro generate-accessor (key-symbol prefix)
  (let ((mac-name 
          (intern (string-upcase (concatenate 'string
                                              prefix "-"
                                              (string key-symbol))))))
    `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))

所以当我尝试的时候——

CL-USER> (generate-accessor 'a "alist")
; ERROR> 'A cannot be coerced to a string.

但是...

CL-USER> (string 'a)
; RESULT> "A"

所以我再次尝试使用 SYMBOL-NAME 将符号强制转换为字符串

(defmacro generate-accessor (key-symbol prefix)
  (let ((mac-name 
          (intern (string-upcase (concatenate 'string
                                              prefix "-"
                                              (symbol-name key-symbol))))))
    `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))

这一次我试试——

CL-USER> (generate-accessor 'a "alist")
; ERROR> The value 'A is not of type SYMBOL.

但是...

CL-USER> (symbol-name 'a)
; RESULT>"A"
CL-USER> (symbolp 'a)
; RESULT>T

每当我'a在宏之外使用时,它都会像我期望的那样自动作为符号进行实习。然而,当我传递'a给我的宏时,它以某种方式作为引用块到达。我不明白为什么不对其进行评估,尤其是在反引号开始之前的某个时间点。我知道我不了解 Lisp 的基本知识,但我现在不知道如何看待它。

4

3 回答 3

12

'a是 的简写(quote a),它是您传递给宏的列表。宏参数不被评估,而是按原样传递。当用作函数(即,不是宏)的参数时,(quote a)首先评估评估 (quote a)的结果是符号a。例如,考虑一下

(list 'a)           ===
(list (quote a)) 
; => (a)

'('a)               ===
'((quote a))        ===   
(quote ((quote a)))
; => ((quote a)) ;; which may also be printed ('a)

对宏使用符号参数的示例

根据评论中的要求,这里有一个类似defstruct- 的宏,它创建了一些包含结构名称的函数。

(defmacro my-defstruct (name slot)
  "A very poor implementation of defstruct for structures
that have exactly one slot"
  (let ((struct-name (string name))
        (slot-name (string slot)))
    `(progn
       (defun ,(intern (concatenate 'string (string '#:make-) struct-name)) (value)
         (list value))
       (defun ,(intern (concatenate 'string (string struct-name) "-" slot-name)) (structure)
         (car structure)))))

例如,以下是(my-defstruct foo bar)扩展为:

CL-USER> (pprint (macroexpand '(my-defstruct foo bar)))

(PROGN
 (DEFUN MAKE-FOO (VALUE) (LIST VALUE))
 (DEFUN FOO-BAR (STRUCTURE) (CAR STRUCTURE)))

使用示例:

CL-USER> (my-defstruct foo bar)
FOO-BAR
CL-USER> (make-foo 34)
(34)
CL-USER> (foo-bar (make-foo 34))
34
于 2013-07-26T00:50:50.350 回答
8

每当我在宏之外使用 'a 时,它会自动作为我期望的符号被实习。

为什么。我希望它是一个引用的形式,并且在评估时我得到引用的对象。

请记住:宏转换源代码,而不是评估对象。

由于您的代码没有评估它并且您调用SYMBOL-NAME了引用的表单,我希望这是一个错误。

您可以像其他代码一样调试宏。打印出宏作为参数得到的内容,或者只使用调试器查看回溯并查看各种变量值。

CHECK-TYPE检查某个地方的类型。

CL-USER 15 > (defmacro generate-accessor (key-symbol prefix)
               (check-type key-symbol symbol)
               (check-type prefix string)
               (let ((mac-name 
                      (intern (string-upcase (concatenate 'string
                                                          prefix "-"
                                                          (string key-symbol))))))
                 `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))
GENERATE-ACCESSOR

CL-USER 16 > (generate-accessor 'a "alist")

Error: The value (QUOTE A) of KEY-SYMBOL is not of type SYMBOL.
  1 (continue) Supply a new value of KEY-SYMBOL.
  2 (abort) Return to level 0.
  3 Return to top loop level 0.

所以CHECK-TYPE抱怨它不是一个符号,而是一个包含两个元素的列表。这是预期的,因为宏适用于源代码,而不是评估的对象。

顺便说一句,将访问器编写为宏已经是错误的。Common Lisp 有功能——使用它们。

于 2013-07-26T06:36:10.643 回答
0

显然,您没有注意到'a在调用宏时未对其进行评估:

(defmacro generate-accessor (**key-symbol** prefix)
  (let ((mac-name 
          (intern (string-upcase (concatenate 'string
                                              prefix "-"
                                              (string **key-symbol**))))))
    `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))

key-symbol只是被替换而不是被评估。例如,定义一个更简单的宏,如下所示:

(defmacro foo (a b)                                                                                          
  (let ((x a) (y b) (z (concatenate 'string (string a) b)))                                                  
    `(print (list ,a ,b ,x ,y ,z))))

传入“a”和“b”:

CL-USER> (foo 'a "b")                                                                                        
; Evaluation aborted on #<SIMPLE-TYPE-ERROR expected-type: SB-KERNEL:STRING-DESIGNATOR datum: 'A>.

在执行之后(setq a 1),传入 a 和 "b":

CL-USER> (foo a "b")                                                               
(1 "b" 1 "b" "Ab")                                                                                           
(1 "b" 1 "b" "Ab")

您可以发现a(不带引号)只是字面上替换let子句中,因为其中没有 eval 逗号。

要解决此问题,您可以传入不带引号的符号,或将宏重写为如下函数:

(defun generate-accessor (key-symbol prefix)
  (eval
    (let ((mac-name 
             (intern (string-upcase (concatenate 'string
                                                 prefix "-"
                                                 (string key-symbol))))))
      `(defmacro ,mac-name (alis) `(assoc ',key-symbol ,alis)))))

但这不是最终答案——key-symbol在保持alis不变的情况下,您将如何评估?我认为这是不可能的。

也许您也可以将内部更改defmacrodefun

(defun generate-accessor (key-symbol prefix)
  (eval
    (let ((mac-name 
           (intern (string-upcase (concatenate 'string
                                               prefix "-"
                                               (string key-symbol))))))
      `(defun ,mac-name (alis) (assoc ',key-symbol alis)))))

有用!

CL-USER> (generate-accessor 'a "x")                                                              
X-A

宏很强大,但不要过度使用!

于 2016-01-22T15:55:06.150 回答