McCarthy's基本S函数和谓词为atom
,eq
,car
,cdr
,cons
然后他继续添加他的基本符号,以便能够写出他所谓的S函数:quote
,cond
,lambda
,label
在此基础上,我们将这些称为“LISP原语”(尽管我对类似谓词的论证持开放态度,如numberp
)
如何在您选择的LISP中仅使用这些原语来定义defmacro
函数? (包括Scheme和Clojure)
答案 0 :(得分:5)
尝试在像McCarthy的LISP机器这样的机器上执行此操作的问题在于,没有办法在运行时阻止参数评估,并且在编译时无法改变事物(这是宏执行的操作:他们在编译之前重新安排代码,基本上。)
但这并不能阻止我们在运行时在McCarthy的机器上重写我们的代码。诀窍是引用我们传递给我们的“宏”的参数,因此它们不会被评估。
作为一个例子,让我们看一下我们可能想要的功能; unless
。我们的理论函数有两个参数p
和q
,并返回q
,除非 p
为真。如果p
为真,则返回nil。
一些例子(在Clojure的语法中,但不会改变任何内容):
(unless (= "apples" "oranges") "bacon")
=> "bacon"
(unless (= "pears" "pears") "bacon")
=> nil
首先,我们可能希望将unless
写为函数:
(defn unless [p q]
(cond p nil
true q))
这似乎工作正常:
(unless true 6)
=> nil
(unless false 6)
=> 6
对于麦卡锡的LISP,它可以正常工作。问题是我们现在不只是在现代Lisps中有无副作用的代码,所以传递给unless
的所有参数都被评估,无论我们是否想要它们,都是有问题的。事实上,即使在McCarthy的LISP中,如果评估年龄要做的其中一个论点,这可能是一个问题,而我们只想很少这样做。但这特别是副作用的问题。
因此,如果unless
为false,我们希望我们的q
评估并仅返回p
。如果我们将q
和p
作为参数传递给函数,我们就无法做到这一点。
但是在我们将它们传递给我们的函数之前我们可以quote
它们,从而阻止了它们的评估。我们可以使用eval
的功能(在我们需要的时候,也只使用参考文章后面的基元定义的基元和其他函数定义)来评估我们需要什么。
所以我们有一个新的unless
:
(defn unless [p q]
(cond (eval p) nil
true (eval q)))
我们使用它有点不同:
(unless (quote false) (quote (println "squid!")))
=> "squid" nil
(unless (quote true) (quote (println "squid!")))
=> nil
在那里你可以慷慨地称之为宏观。
但这不是defmacro
或其他语言的等效词。那是因为在McCarthy的机器上,在编译时没有办法执行代码。如果您使用eval
函数评估代码,则无法知道不将参数评估为“宏”函数。阅读和评估之间的区别与现在不同,尽管这个想法存在。在quote
的酷炫和eval
的列表操作中,“重写”代码的能力就在那里,但它并没有像现在这样用语言实习(我' d称它为语法糖,差不多:只引用你的论点,你就有了宏观系统的力量。)
我希望我已经回答了你的问题,而没有试图用自己的原语来定义一个体面的defmacro
。如果你真的想看到这一点,我会指出你在Clojure来源中难以理解的source for defmacro
,或者更多的Google。
答案 1 :(得分:2)
在其所有细节中完全解释它需要大量的空间和时间来回答这里,但是大纲非常简单。每个LISP最终都有一些类似于READ-EVAL-PRINT循环的东西,也就是说,逐个元素,逐个元素地解释它,并改变状态 - 在内存中或通过打印结果。
读取部分查看读取的每个元素并使用它执行某些操作:
(cond ((atom elem)(lambda ...))
((function-p elem) (lambda ...)))
要解释宏,你只需(?)需要实现一个函数,将宏的模板文本放在存储中的某个地方,这个repl循环的谓词 - 这意味着简单地定义一个函数 - 说“哦,这是一个宏!“,然后将该模板文本复制回阅读器,以便进行解释。
如果您真的想看到毛茸茸的细节,请阅读计算机程序的结构和解释或阅读Queinnec的Lisp in Small PIeces。