macros - `apply` 或 `funcall` 用于宏而不是函数
问题描述
在 Lisp 中,函数的参数在进入函数体之前首先被求值。宏参数保持不被评估。
但有时,人们想将存储在变量中的代码片段注入到宏中。这意味着首先评估宏的参数,然后将选择的宏应用于此评估结果。
不得不求助于
(eval `(macro ,arg))
实现这一点 - 但eval
在不同的环境中行为不正确。
最好的事情是,如果可以做到:
(apply macro (list arg))
或者
(funcall macro arg)
但是由于宏不是一个函数,所以这是行不通的。
有可能实现这样的目标吗?- 为了规避这个问题,让宏在函数命名空间中可用?
还是我错过了解决此类问题的其他方法?
我在尝试回答如何从列表中生成 HTML 时遇到了这个问题。但也在通用 lisp 中使用宏生成 TYPECASE 中,评估传递给在 lisp 中生成函数的宏的参数,以及如何在方案中将列表转换为代码/lambda?. 但我一直认为,在回答他们时,最好有一个可以接受宏的apply
或类似的函数。funcall
解决方案
目前尚不清楚您要做什么,尽管几乎可以肯定您对某些事情感到困惑。特别是如果您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 所做的。尤其是认为像 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
不仅不是解决几乎所有问题的糟糕解决方案:它还是核武器。对于某些问题,核武器也许是很好的解决方案,但它们很少而且相差甚远。
推荐阅读
- elm - 是否可以在自定义类型的标签上使用“case ... of”语句?
- gradle - Kotlin native protobuf problems
- database - 在模式更新上创建 Hibernate 自动 SQL 迁移脚本以与 flyway 一起使用
- java - 无法在 Intellij 2019.1.3 Ultimate Edition 中安装 maven 依赖项
- asp.net - 在 asp.net 中使用 web 配置的 url 路由
- javascript - Javascript将循环中的数据值与if进行比较
- python - 如何编写同步和异步代码的类实例化形式?
- python - 在 tensorflow 中使用迭代器生成特征和标签
- python - 为什么我会在时间序列分析中得到如此奇怪的结果?
- python - 当我使用 python3 时出现 ModuleNotFoundError