Macros 公共Lisp:将符号传递给宏

Macros 公共Lisp:将符号传递给宏,macros,arguments,metaprogramming,common-lisp,symbols,Macros,Arguments,Metaprogramming,Common Lisp,Symbols,此宏的目的是创建一个宏,为访问关联列表的某个键提供名称 (defmacro generate-accessor (key-symbol prefix) (let ((mac-name (intern (string-upcase (concatenate 'string prefix "-"

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

(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"
CL-USER> (symbol-name 'a)
; RESULT>"A"
CL-USER> (symbolp 'a)
; RESULT>T
因此,我再次尝试使用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> (string 'a)
; RESULT> "A"
CL-USER> (symbol-name 'a)
; RESULT>"A"
CL-USER> (symbolp 'a)
; RESULT>T

每当我在宏之外使用
'a
时,它就会像我所期望的那样自动成为一个符号。然而,不知何故,当我将
'a
传递给我的宏时,它作为一个带引号的块到达。我不明白为什么没有对它进行评估,尤其是在反报价开始之前。我知道我不了解Lisp的一些基本内容,但我现在不知道如何查看它。

'a
(引号a)
的缩写,它是传递给宏的列表。不计算宏参数,但按原样传递。当用作函数(即,不是宏)的参数时,首先计算
(引号a)
,计算
(引号a)
的结果是符号
a
。例如,考虑

之间的差异。
(list 'a)           ===
(list (quote a)) 
; => (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
每当我在宏的外部使用“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.
因此,
检查类型
抱怨它不是一个符号,而是两个元素的列表。 这是意料之中的,因为宏处理源代码,而不是计算对象


顺便说一句,将访问器写入宏已经是错误的。Common Lisp有函数-请使用它们。

显然,在调用宏时,您没有注意到未对
'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))))
键符号只是被替换而不是计算。例如,定义一个更简单的宏,如下所示:

(defmacro foo (a b)                                                                                          
  (let ((x a) (y b) (z (concatenate 'string (string a) b)))                                                  
    `(print (list ,a ,b ,x ,y ,z))))
输入“a”和“b”:

您可以看出,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)))))
但这不是最终的答案——在保持
alis
不变的情况下,您将如何评估
关键符号
?我认为这是不可能的

也许您也可以将内部的
defmacro
更改为
defun

(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

宏功能强大,但不要过度使用

我不能说我同意这是错误的。我的目标是使用
GENERATE-ACCESSOR
获得类似于
DEFSTRUCT
创建的访问器的结果,但用于关联列表。最后,我可能没有为自己节省任何东西,但我想知道为什么我的实验不起作用。感谢您的回答和演示
CHECK-TYPE
@SquareCrow:DEFSTRUCT访问器是函数,而不是宏。好的,很高兴知道这一点。宏不计算它们的参数。您认为有什么方法可以实现动态生成宏名称的目标,就像
DEFSTRUCT
那样吗?@SquareCrow我用一个例子更新了答案。虽然为宏生成名称很容易,但我要指出,
defstruct
实际上是在为函数生成名称。我明白了,也许我可以生成函数。在阅读了你和雷纳给出的答案后,我得出了一个类似的解决方案。我的初衷是通过符号列表在
DOLIST
中创建宏,并基于每个符号创建访问器名称。我看到迭代变量的符号,而不是变量内容,被传递到宏。我还了解了
DEFSTRUCT
,它通过符号
A
传递,而不引用符号
'A
(正如我第一次尝试的那样)可以从这些符号构造函数名。我也怀疑我对灵活的“主义结构”的追求。
CL-USER> (generate-accessor 'a "x")                                                              
X-A