Common Lisp:将符号传递给宏
Posted
技术标签:
【中文标题】Common Lisp:将符号传递给宏【英文标题】:Common Lisp: Passing Symbol to Macro 【发布时间】:2013-07-26 02:52:05 【问题描述】:这个宏的目的是创建一个宏,为访问关联列表的某个键提供一个名称。
(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 的基本知识,但我现在不知道如何看待它。
【问题讨论】:
【参考方案1】:'a
是 (quote a)
的简写,它是您传递给宏的 list。宏参数不被评估,而是按原样传递。当用作函数(即,不是宏)的参数时,(quote a)
首先被求值,求值的结果(quote a)
是符号@987654325 @。例如,考虑一下
(list 'a) ===
(list (quote a))
; => (a)
和
'('a) ===
'((quote a)) ===
(quote ((quote a)))
; => ((quote a)) ;; which may also be printed ('a)
在宏中使用符号参数的示例
根据 cmets 中的请求,这里有一个类似 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
【讨论】:
好的,很高兴知道。宏不评估他们的论点。您是否认为我可以像DEFSTRUCT
那样实现动态生成宏名称的目标?
@SquareCrow 我已经用一个例子更新了答案。虽然为宏生成名称很容易,但我要指出 defstruct
实际上是为函数生成名称。
我明白了,也许我可以生成函数。在阅读了你自己和 Rainer 给出的答案后,我得出了一个类似的解决方案。我最初的意图是通过符号列表在DOLIST
中使用我的宏,并根据每个符号创建访问器名称。我看到迭代变量的符号,而不是变量内容,被传递给宏。我还看到DEFSTRUCT
,它传递符号A
而不是引用符号'A
(在我的第一次尝试中)可能会从这些符号构造函数名称。我也怀疑我对灵活“alist struct”的追求。【参考方案2】:
每当我在我的宏之外使用 '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 有函数 - 使用它们。
【讨论】:
不能说我同意这是错误的。我的目标是使用GENERATE-ACCESSOR
获得类似于DEFSTRUCT
创建的访问器的结果,但用于关联列表。我可能最终没有为自己保住任何东西,但我想知道为什么我的实验没有奏效。感谢您的回答和演示CHECK-TYPE
。
@SquareCrow:DEFSTRUCT 访问器是函数,而不是宏。【参考方案3】:
显然您没有注意到调用宏时未评估 '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
不变?我认为这是不可能的。
也许您也可以将内部 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
宏很强大,但不要过度使用!
【讨论】:
以上是关于Common Lisp:将符号传递给宏的主要内容,如果未能解决你的问题,请参考以下文章