目前尚不清楚您要做什么,尽管几乎可以肯定您对某些事情感到困惑。特别是如果您eval
在宏扩展内部调用,那么在几乎所有情况下,您都在做一些严重错误和严重危险的事情。我从来没有想过我想要宏扩展到包括在内的东西的情况,eval
并且我已经编写了 Lisp 很长时间了。
话虽如此,这就是您如何调用与宏相关联的函数,以及为什么它很少是您想要做的。
宏只是其域和范围是源代码的函数:它们是从一种语言到另一种语言的编译器。完全可以调用与宏关联的函数,但是该函数将返回源代码,然后您需要对该源代码进行评估。如果您想要一个处理不是源代码的运行时数据的函数,那么您需要该函数,并且您不能通过某种魔术将宏转换为该函数,这似乎是您想要做的:魔术不存在,也不可能存在。
例如,如果我有一个宏
(defmacro with-x (&body forms)
`(let ((x 1))
,@forms))
然后我可以在一点源代码上调用它的宏函数:
> (funcall (macro-function 'with-x)
'(with-x (print "foo")) nil)
(let ((x 1)) (print "foo"))
但是这样做的结果是另外一点源代码:我需要编译或评估它,而我无能为力。
确实在(几乎?)所有情况下,这与macroexpand-1
)相同:
> (macroexpand-1 '(with-x (print "foo")))
(let ((x 1)) (print "foo"))
t
你可能可以这样写macroexpand-1
:macro-function
(defun macroexpand-1/equivalent (form &optional (env nil))
(if (and (consp form)
(symbolp (first form))
(macro-function (first form)))
(values (funcall (macro-function (first form)) form env)
t)
(values form nil)))
那么,如果调用宏的结果是源代码,那么你如何处理该源代码以获得不是源代码的结果?好吧,你必须评估它。然后,好吧,既然评估器无论如何都会为你扩展宏,你不妨写一些类似的东西
(defun evaluate-with-x (code)
(funcall (compile nil `(lambda ()
(with-x ,@code)))))
所以你在任何情况下都不需要调用宏的函数。这并不是将宏转换为处理不是源代码的数据的函数的魔术:这是一个完全由爆炸部分组成的可怕恐怖。
看起来这个问题可能源于这个问题,而潜在的问题是这不是 CL-WHO 所做的。尤其是认为像 CL-WHO 这样的工具是用于获取某种列表并将其转换为 HTML 的工具,这是一种困惑。它不是:它是一种获取基于 CL 的语言的源代码的工具,但包括一种表达与 CL 代码混合的 HTML 输出的方式,并将其编译成 CL 代码,它会做同样的事情。恰好是 CL 源代码表示为列表和符号的情况,但 CL-WHO 并不是真的:它是一个从“CL-WHO 语言”到 CL 的编译器。
所以,让我们试试我们上面尝试的技巧,看看为什么它是一场灾难:
(defun form->html/insane (form)
(funcall
(compile nil `(lambda ()
(with-html-output-to-string (,(make-symbol "O"))
,@form)))))
如果你没有仔细观察这个,你可能会认为这个函数实际上确实起到了神奇的作用:
> (form->html/insane '(:p ((:a :href "foo") "the foo")))
"<p></p><a href='foo'>the foo</a>"
但事实并非如此。如果我们调用form->html/insane
这个完全无害的列表会发生什么:
(:p (uiop/run-program:run-program "rm -rf $HOME" :output t))
提示:如果您没有很好的备份,请不要访问form->html/insane
此列表。
CL-WHO 是一种编程语言的实现,它是 CL 的严格超集:如果您尝试将其转换为将列表转换为 HTML 的函数,那么您最终会得到涉及每次调用时都在修补的同一个核武器eval
,除了核武器藏在一个锁着的柜子里,你看不到它。但它并不关心这一点:如果你把它点燃,它仍然会将几英里内的所有东西都变成放射性灰烬和瓦砾。
因此,如果您想要一个将列表(不是源代码的列表)转换为 HTML 的工具,请编写该工具。CL-WHO 在其实施中可能有这样一个工具的胆量,但你不能按原样使用它。
每当您试图以这种方式滥用宏时,您都会遇到同样的问题:调用宏函数的结果是Lisp 源代码,并评估您需要的源代码eval
或eval
. 而且eval
不仅不是解决几乎所有问题的糟糕解决方案:它还是核武器。对于某些问题,核武器也许是很好的解决方案,但它们很少而且相差甚远。