【问题标题】:`apply` or `funcall` for macros instead of functions`apply` 或 `funcall` 用于宏而不是函数
【发布时间】:2021-11-15 10:06:49
【问题描述】:

在 Lisp 中,函数的参数在进入函数体之前首先被求值。宏参数保持不被评估。

但有时,人们想将存储在变量中的代码片段注入到宏中。这意味着首先评估宏的参数,然后将选择的宏应用于此评估结果。

不得不求助

(eval `(macro ,arg))

要实现这一点 - 但eval 在不同环境中的行为不正确。

如果可以做到,最好的事情就是:

(apply macro (list arg))

(funcall macro arg)

但是由于宏不是函数,所以这不起作用。

有可能实现这样的目标吗? - 为了规避这个问题,让宏在函数命名空间中可用?

还是我错过了解决此类问题的其他方法?

我在尝试回答How to produce HTML from a list. 以及Generate TYPECASE with macro in common lispEvaluate arguments passed to a macro that generates functions in lispHow to convert a list to code/lambda in scheme? 时遇到了这个问题。但我一直认为,在回答他们时,最好有一个可以接受宏的 applyfuncall 类函数。

【问题讨论】:

  • 可以funccall一个宏的宏函数,就是一个函数。但是,您所描述的几乎可以肯定是一个设计问题或对宏的用途的误解。
  • @ignisvolens 中存在多少误解或设计问题?你能举一些例子吗? - 我会说宏用于操作代码。但有时人们想在运行时动态地操作代码。有时 - 在运行时之前不知道要操作哪个代码。然后,必须在运行时生成的一段代码上运行宏。
  • @ignisvolens 如何获得宏的宏功能?
  • @Gwang-JinKim:请您提供一个最低限度的可重现示例来说明您要实现的目标
  • @FrancisKing 我相信这个问题与How to produce HTML from a list中的问题有关。

标签: macros common-lisp apply eval funcall


【解决方案1】:

不清楚您要做什么,尽管几乎可以肯定您对某事感到困惑。特别是如果您在宏扩展中调用eval,那么在几乎所有 情况下,您正在做一些严重错误和严重危险的事情。我从来没有想过我想要宏扩展到包括eval 在内的东西,而且我已经编写 Lisp 很长时间了。


话虽如此,下面是您如何调用与宏关联的函数,以及为什么它很少是您想要做的。

宏只是其域和范围是源代码的函数:它们是从一种语言到另一种语言的编译器。完全可以调用与宏关联的函数,但该函数将返回源代码,然后您需要对该源代码进行评估。如果您想要一个处理不是源代码的运行时 data 的函数,那么您需要该函数,并且您不能通过某种魔术将宏转换为该函数,这似乎是你想做什么:那个魔术不存在,也不可能存在

例如,如果我有一个宏

(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

看起来这个问题可能起源于this one,而潜在的问题是这不是 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-&gt;html/insane 会发生什么:

(:p (uiop/run-program:run-program "rm -rf $HOME" :output t))

提示:如果您没有很好的备份,请不要在此列表中致电 form-&gt;html/insane

CL-WHO 是一种编程语言的实现,它是 CL 的严格超集:如果您尝试将其转换为将列表转换为 HTML 的函数,您最终会得到涉及您每次修补的相同核武器的东西你打电话给eval,除了核武器藏在一个锁着的柜子里,你看不到它。但它并不关心这一点:如果你把它点燃,它仍然会将几英里内的所有东西都变成放射性灰烬和瓦砾。

因此,如果您想要一个将列表(不是源代码的列表)转换为 HTML 的工具,请编写该工具。 CL-WHO 在实现过程中可能有这种工具的胆量,但您不能按原样使用它。


当你试图以这种方式滥用宏时,你会遇到同样的问题:调用宏的函数的结果是 Lisp 源代码,并评估你需要的源代码 @987654339 @ 或 eval 的等效项。而eval 不仅不是解决几乎所有问题的糟糕解决方案:它还是核武器。对于某些问题,核武器可能是很好的解决方案,但它们很少而且相差甚远。

【讨论】:

  • 感谢您的回答。我明白什么是宏。而且我知道评估其参数的宏应该是一个函数。但是有时(请参阅提供的链接)您想“滥用”宏并将其用作函数。我们知道with-html-output-to-string是一个用于构建html代码的宏。但是正如提问的人所问的那样 - 如果一个人已经将表达式作为一个列表,以及当一个人想要在这个预先形成的表达式上应用宏时怎么办。应该可以实现这一点——因为 lisp 声称可以随意修改和折叠。
  • 当然可以编写with-html-output-to-string 的函数版本,但这可能会成为很多工作。并且知道宏和函数之间的唯一区别是在将参数提供给宏之前对其进行一次评估。我只是在寻找这个适配器。我看不出有什么理由应该以这种方式限制运行宏。也可以在运行时动态调用宏。
  • @Gwang-JinKim:你可以问这个问题,恐怕意味着你真的不明白宏是什么,因为有人不会问。 Lisp 确实是非常可变的,但即使在 Lisp 中魔法也是不可能的。不幸的是,认为自己理解某事但实际上并不理解的人通常是最难向其解释的人:我希望我的回答对您有所帮助,但我怀疑它可能对其他人有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-05
相关资源
最近更新 更多