首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于宏而不是函数的`apply`或`funcall`

用于宏而不是函数的`apply`或`funcall`
EN

Stack Overflow用户
提问于 2021-11-15 10:06:49
回答 1查看 123关注 0票数 0

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

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

人们不得不求助于

代码语言:javascript
复制
(eval `(macro ,arg))

但是eval在不同的环境中不能正确运行。

最好的事情是,如果可以的话:

代码语言:javascript
复制
(apply macro (list arg))

代码语言:javascript
复制
(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-like函数。

EN

回答 1

Stack Overflow用户

发布于 2021-11-15 16:04:23

虽然几乎可以肯定的是你对某些事情感到困惑,但并不清楚你想要做什么。特别是,如果您在宏扩展中调用eval,那么在几乎所有的情况下,您都在做一些严重错误且非常危险的事情。我想不出有哪种情况下我需要扩展到包括eval在内的宏,而且我已经编写Lisp很长一段时间了。

也就是说,下面是你如何调用与宏相关的函数,以及为什么它很少是你想要做的。

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

举个例子,如果我有一个宏

代码语言:javascript
复制
(defmacro with-x (&body forms)
  `(let ((x 1))
     ,@forms))

然后我可以在一些源代码上调用它的宏函数:

代码语言:javascript
复制
> (funcall (macro-function 'with-x)
                     '(with-x (print "foo")) nil)
(let ((x 1)) (print "foo"))

但这样做的结果是另一小部分源代码:我需要编译或评估它,而我所做的任何事情都无法绕过这一点。

确实在(几乎?)所有情况下,这与macroexpand-1相同):

代码语言:javascript
复制
> (macroexpand-1 '(with-x (print "foo")))
(let ((x 1)) (print "foo"))
t

您也许可以用macro-function编写macroexpand-1

代码语言:javascript
复制
(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)))

那么,如果调用宏的结果是源代码,那么如何处理该源代码才能得到非源代码的结果呢?嗯,你必须对它进行评估。然后,既然赋值器无论如何都会为您展开宏,您也可以编写类似这样的代码

代码语言:javascript
复制
(defun evaluate-with-x (code)
  (funcall (compile nil `(lambda ()
                           (with-x ,@code)))))

所以你在任何情况下都不需要调用宏的函数。这不是将宏转换为处理非源代码数据的函数的魔术:这是一个可怕的恐怖,完全是由爆炸部件组成的。

一个具体的例子:CL-WHO

看起来这个问题可能起源于this one,潜在的问题是这并不是CL-WHO所做的。特别是,认为像CL-WHO这样的东西是用于获取某种列表并将其转换为HTML的工具,这是一种混乱。它不是:它是一种工具,用于获取构建在CL上的语言的源代码,但包括一种表达HTML输出与CL代码混合的方式,并将其编译成CL代码,从而实现相同的功能。碰巧CL源代码被表示为列表和符号,但CL-WHO并不是这样的:如果你喜欢,它是一个从“CL -WHO语言”到CL的编译器。

因此,让我们尝试一下上面尝试过的技巧,看看为什么它是一场灾难:

代码语言:javascript
复制
(defun form->html/insane (form)
  (funcall 
   (compile nil `(lambda () 
                   (with-html-output-to-string (,(make-symbol "O"))
                     ,@form)))))

如果你没有仔细观察,你可能会认为这个函数确实起到了魔术的作用:

代码语言:javascript
复制
> (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的严格超集:如果你试图把它变成一个函数,把列表变成超文本标记语言,你最终会得到一些涉及你每次调用eval时修补的核武器的东西,除了核武器藏在一个上了锁的柜子里,你看不到它。但它并不关心这一点:如果你引爆了它,它仍然会把方圆几英里内的一切都变成放射性的火山灰和碎石。

所以,如果你想要一个可以把列表--非源代码的列表--转换成HTML的工具,那就写这个工具吧。CL-WHO在它的实现中可能有这样一个工具的胆量,但你不能直接使用它。

这就是您试图以这种方式滥用宏时所面临的相同问题:调用宏的函数的结果是Lisp源代码,要评估该源代码,您需要evaleval的等价物。对于几乎所有问题,eval不仅不是一个可怕的解决方案:它还是核武器。也许有一些问题,核武器是很好的解决方案,但它们很少。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69972590

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档