仅使用 LISP 原语定义 defmacro 函数?
McCarthy 的 基本 S 函数和谓词是 atom
, eq
, car
, cdr
, cons
然后他继续添加到他的基本符号中,以便能够编写什么他调用了 S-functions:quote
、cond
、lambda
、label
在此基础上,我们将这些称为“LISP 基元”(尽管我对关于类型谓词的争论持开放态度,例如 numberp
),
您将如何在您的 LISP 中仅使用这些基元来定义 defmacro
函数?选择? (包括Scheme和Clojure)
McCarthy's Elementary S-functions and predicates were atom
, eq
, car
, cdr
, cons
He then went on to add to his basic notation, to enable writing what he called S-functions: quote
, cond
, lambda
, label
On that basis, we'll call these "the LISP primitives" (although I'm open to an argument about type predicates like numberp
)
How would you define the defmacro
function using only these primitives in the LISP of your choice? (including Scheme and Clojure)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
尝试在像 McCarthy 的 LISP 机器这样的机器上执行此操作的问题是,没有办法阻止运行时的参数求值,也没有办法在编译时改变事情(这就是宏的作用:它们重新排列代码)基本上是在编译之前)。
但这并不能阻止我们在麦卡锡的机器上运行时重写代码。诀窍是引用我们传递给“宏”的参数,这样它们就不会被评估。
作为一个例子,让我们看一下我们可能想要的一个函数;
除非
。我们的理论函数接受两个参数,p
和q
,并返回q
unlessp
> 是真的。如果 p 为 true,则返回 nil。一些示例(使用 Clojure 的语法,但这不会改变任何内容):
所以一开始我们可能想将
unless
写为函数:这似乎工作得很好:
对于 McCarthy 的 LISP,它会工作得很好。问题在于,我们在现代 Lisp 中不仅仅有无副作用的代码,因此传递给
unless
的所有参数都被求值,无论我们是否希望它们这样做,这一事实都是有问题的。事实上,即使在 McCarthy 的 LISP 中,如果评估其中一个参数需要花费很长时间,而我们只想很少这样做,这也可能是一个问题。但这尤其是一个副作用问题。因此,我们希望
unless
在p
为 false 时评估并返回q
仅。如果我们将q
和p
作为参数传递给函数,我们就无法做到这一点。但是我们可以在将它们传递给我们的函数之前
引用
它们,从而阻止它们的求值。当我们需要时,我们可以使用 eval 的强大功能(也已定义,仅使用原语和参考论文后面使用原语定义的其他函数)来评估我们需要的内容。所以我们有一个新的
unless
:我们使用它的方式有点不同:
这样你就有了可以被慷慨地称为宏的东西。
但这不是
defmacro
或其他语言中的等效项。那是因为在麦卡锡的机器上,没有办法在编译时执行代码。如果您使用 eval 函数评估代码,它就无法知道不评估“宏”函数的参数。尽管这个想法已经存在,但阅读和评估之间并没有像现在那样的区别。 “重写”代码的能力是存在的,在quote
的酷炫以及与eval
结合的列表操作中,但它并没有被嵌入到语言中,因为现在就是了(我称之为语法糖,几乎是:只需引用你的论点,你就拥有了宏观系统的力量。)我希望我已经回答了你的问题,而不是试图定义一个像样的 < code>defmacro 我自己使用这些原语。如果你真的想看到这一点,我会向你指出难以理解的 Clojure 源代码中的
defmacro
源代码,或者 Google 更多信息。The problem with trying to do this on a machine like McCarthy's LISP machine is that there isn't a way to prevent argument evaluation at runtime, and there's no way to change things around at compile time (which is what macros do: they rearrange code before it's compiled, basically).
But that doesn't stop us from rewriting our code at runtime on McCarthy's machine. The trick is to quote the arguments we pass to our "macros", so they don't get evaluated.
As an example, let's look at a function we might want to have;
unless
. Our theoretical function takes two arguments,p
andq
, and returnsq
unlessp
is true. Ifp
is true, then return nil.Some examples (in Clojure's syntax, but that doesn't change anything):
So at first we might want to write
unless
as a function:And this seems to work just fine:
And with McCarthy's LISP, it would work just fine. The problem is that we don't just have side-effectless code in our modern day Lisps, so the fact that all arguments passed to
unless
are evaluated, whether or not we want them to, is problematic. In fact, even in McCarthy's LISP, this could be a problem if, say, evaluating one of the arguments took ages to do, and we'd only want to do it rarely. But it's especially a problem with side-effects.So we want our
unless
to evaluate and returnq
only ifp
is false. This we can't do if we passq
andp
as arguments to a function.But we can
quote
them before we pass them to our function, preventing their evaluation. And we can use the power ofeval
(also defined, using only the primitives and other functions defined with the primitives later in the referenced paper) to evaluate what we need to, when we need to.So we have a new
unless
:And we use it a little differently:
And there you have what could generously be called a macro.
But this isn't
defmacro
or the equivalent in other languages. That's because on McCarthy's machine, there wasn't a way to execute code during compile-time. And if you were evaluating your code with theeval
function, it couldn't know not to evaluate arguments to a "macro" function. There wasn't the same differentiation between the reading and the evaluating as there is now, though the idea was there. The ability to "re-write" code was there, in the coolness ofquote
and the list operations in conjunction witheval
, but it wasn't interned in the language as it is now (I'd call it syntactic sugar, almost: just quote your arguments, and you've got the power of a macro-system right there.)I hope I've answered your question without trying to define a decent
defmacro
with those primitives myself. If you really want to see that, I'd point you to the hard-to-grok source fordefmacro
in the Clojure source, or Google around some more.在这里全面解释它的所有细节将需要大量的空间和时间来回答,但大纲确实非常简单。每个 LISP 最终都会在其核心中包含类似 READ-EVAL-PRINT 循环的东西,也就是说,它会逐个元素地获取列表、解释它并更改状态(在内存中或通过打印结果)。
读取部分查看读取的每个元素并对其执行某些操作:
要解释宏,您只需(?)实现一个函数,将宏的模板文本放入存储中的某个位置,这是该 repl 循环的谓词 - 这意味着只需定义一个函数,即“哦,这是一个宏!”,然后将该模板文本复制回阅读器中,以便对其进行解释。
如果您确实想了解详细信息,请阅读计算机程序的结构和解释或阅读 Queinnec 的 Lisp 小块。
Explaining it fully in all its details would require an awful lot of space and time for an answer here, but the outline is really pretty simple. Every LISP eventually has in its core something like the READ-EVAL-PRINT loop, which is to say something that takes a list, element by element, interprets it, and changes state -- either in memory or by printing a result.
The read part looks at each element read and does something with it:
To interpret macros, you simply (?) need to implement a function that puts the template text of the macro somewhere in storage, a predicate for that repl loop -- which means simply defining a function -- that says "Oh, this is a macro!", and then copy that template text back into the reader so it's interpreted.
If you really want to see the hairy details, read Structure and Interpretation of Computer Programs or read Queinnec's Lisp in Small PIeces.