2010-08-21 70 views
11

McCarthy's小学S-功能和谓语​​,eqcarcdrcons仅使用LISP基元定义defmacro函数?

接着,他添加到他的基本符号,使书写了他所谓的S-功能:quotecondlambdalabel

在此基础上,我们将称之为“LISP的原语”(虽然我开约型谓词像numberp参数)

如何你会在你选择的LISP中仅使用这些基元来定义defmacro函数吗? (包括Scheme和Clojure)

+1

'Defmacro'是一个宏本身,而不是一个函数。 – Svante 2010-08-21 03:12:21

+0

@Svante不是真的:http://github.com/clojure/clojure/blob/b578c69d7480f621841ebcafdfa98e33fcb765f6/src/clj/clojure/core.clj#L370 但它确实有一些可疑的宏观像Java的基础。这是一个令人费解的混乱。 – Isaac 2010-08-21 04:40:17

+0

@J G你的问题总是很有趣!爱参与回答他们的想法:) – Isaac 2010-08-21 04:41:46

回答

5

试图在像McCarthy的LISP机器这样的机器上执行此操作的问题在于,没有办法在运行时阻止参数评估,并且无法在编译时更改这些东西(这是宏执行的操作:它们在编译之前重新排列代码,基本上)。

但是这并不能阻止我们在运行时在McCarthy的机器上重写我们的代码。诀窍是引用我们传递给我们的“宏”的参数,所以它们不会被评估。

作为一个例子,我们来看看我们可能想要的函数; unless。我们的理论函数有两个参数,pq,并且返回q,除非p为真。如果p为真,则返回零。

一些例子(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,它只会工作精细。问题是我们现代Lisp中不仅没有副作用代码,所以所有传递给unless的参数都会被评估,无论我们是否希望它们是有问题的。事实上,即使在McCarthy的LISP中,如果评估其中一个参数需要年龄,那么这可能会成为一个问题,我们只想很少这样做。但这是一个副作用问题。

因此,我们希望我们的unless评估并返回q只有如果p是假的。如果我们通过qp作为函数的参数,我们不能这样做。

但是我们可以在他们将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一起的列表操作中,但它并没有像现在这样被用在语言中(我称之为语法糖,几乎是:只是引用你的论点,而你已经拥有了宏观系统的力量。)

我希望我已经回答了你的问题,而没有试图自己定义一个像样的defmacro。如果你真的想看到这一点,我会告诉你在Clojure源代码中很难操作的source for defmacro,或者是Google的其他代码。

2

详细解释它的所有细节需要很多空间和时间才能在这里找到答案,但大纲非常简单。每个LISP最终的核心就像READ-EVAL-PRINT循环一样,这就是说,一个元素一个元素,一个元素一个元素,解释它,并且改变状态 - 无论是在内存中还是打印结果。

读部分着眼于每个元素阅读和做一些事情吧:(?)

(cond ((atom elem)(lambda ...)) 
     ((function-p elem) (lambda ...))) 

为了解释宏,你只需要实现放入存储宏某处的模板文本的功能,这个repl循环的谓词 - 意思是简单地定义一个函数 - 表示“哦,这是一个宏!”,然后将该模板文本复制回读取器,以便解释它。

如果你真的想看到多毛的细节,请阅读计算机程序的结构和解释或阅读Queinnec的Lisp in Small PIeces

+0

是否SCICP走在关于宏的深度?通过快速搜索,我无法在PDF中找到很多关于它们的内容。 – Isaac 2010-08-21 04:40:58

+0

这可能是过于简单化了,但是你是否在说如果我编写了自己的eval函数,那么我会是80%的方法,前提是我的eval函数知道什么是宏声明,以及后续调用该宏是什么。 – hawkeye 2010-08-21 13:32:46

+0

如果您希望宏的语义与CL/Clojure匹配,那么'eval'将首先在窗体上调用'macroexpand-all',然后将结果传递给不需要了解宏的原始eval。 我已经用这种方式实现了一个小的lisp,它工作得很好。 – Brian 2010-08-21 14:48:23