Clojure 如何实现Lisp宏系统?

Clojure 如何实现Lisp宏系统?,clojure,macros,lisp,Clojure,Macros,Lisp,我已经在node.js上实现了我自己的Lisp,我可以像这样运行s表达式: (assert (= 3 (+ 1 2))) (def even? (fn [n] (= 0 (bit-and n 1)))) (assert (even? 4)) (assert (= false (even? 5))) (断言(=3(+12))) (def偶数?(fn[n](=0(位和n1))) (断言(偶数?4)) (断言(=假(偶数?5))) 现在我想添加宏--defmacro函数,但这就是我遇到的问题。我想

我已经在node.js上实现了我自己的Lisp,我可以像这样运行s表达式:

(assert (= 3 (+ 1 2))) (def even? (fn [n] (= 0 (bit-and n 1)))) (assert (even? 4)) (assert (= false (even? 5))) (断言(=3(+12))) (def偶数?(fn[n](=0(位和n1))) (断言(偶数?4)) (断言(=假(偶数?5))) 现在我想添加宏--
defmacro
函数,但这就是我遇到的问题。我想知道宏系统是如何在其他Lisp中实现的,但我找不到很多指针(除了和)

我看过Clojure宏系统——我最熟悉的Lisp语言——但这似乎太复杂了,我找不到其他可以轻易应用的线索(Clojure宏最终编译成字节码,而字节码不适用于javascript,我也无法理解
macroexpand1
函数)

所以我的问题是:给定一个没有宏但有AST的Lisp实现,如何添加像Clojure的宏系统这样的宏系统?这个宏系统可以用Lisp实现,还是需要用宿主语言实现额外的功能


还有一句话:我还没有实现
quote
),因为我无法确定返回列表中应该包含哪些类型的值。是否应该包含AST元素或对象,如
Symbol
Keyword
(后者是Clojure的情况)?

宏所做的就是将未计算的表单作为参数,并在其主体上执行替换。实现宏系统的诀窍是告诉您的编译器

换句话说,当编译器遇到函数时,它首先计算其形式参数列表,生成结果并将其传递给函数。当编译器找到宏时,它会将未计算的参数传递给主体,然后执行主体请求的任何计算,最后用这些计算的结果替换自身

例如,假设您有一个函数:

(defun print-3-f (x) (progn (princ x) (princ x) (princ x)))
和一个宏:

(defmacro print-3-m (x) `(progn (princ ,x) (princ ,x) (princ ,x)))
然后,您可以立即看到差异:

CL-USER> (print-3-f (rand))
* 234
* 234
* 234

CL-USER> (print-3-m (rand))
* 24
* 642
* 85
要理解为什么会这样,您需要在头脑中运行编译器

当Lisp遇到函数时,它会构建一个树,首先在树中计算
(rand)
,然后将结果传递给函数,函数会将所述结果打印三次

另一方面,当Lisp遇到宏时,它将表单
(rand)
原封不动地传递给主体,主体返回一个带引号的列表,其中
x
(rand)
替换,产生:

(progn (princ (rand)) (princ (rand)) (princ (rand)))
并替换此新窗体的宏调用


您将在各种语言(包括Lisp)中找到大量有关宏的文档。

您需要在计算链中有一个宏扩展阶段:

text-input -> read -> macroexpand -> compile -> load
请注意,宏展开应该是递归的(宏展开,直到没有任何宏可展开的内容为止)


您的环境需要能够“保留”宏扩展函数,在这个阶段可以按名称查找这些函数。请注意,
defmacro
是Common Lisp中的宏本身,它设置了正确的调用,以将名称与该环境中的宏扩展函数相关联。

请看一个示例。这是一个弧形编译器的玩具实现,具有良好的宏支持。

这是Peter Norvig的作品,是任何LISP程序员书架上必不可少的一本书

他假设您正在实现一种解释语言,并提供了一个在LISP中运行的Scheme解释器示例

显示他如何将宏添加到主
eval
函数(
interp

以下是在处理宏之前解释S表达式的函数:

(defun interp (x &optional env)
  "Interpret (evaluate) the expression x in the environment env."
  (cond
    ((symbolp x) (get-var x env))
    ((atom x) x)
    ((case (first x)
       (QUOTE  (second x))
       (BEGIN  (last1 (mapcar #'(lambda (y) (interp y env))
                              (rest x))))
       (SET!   (set-var! (second x) (interp (third x) env) env))
       (IF     (if (interp (second x) env)
                   (interp (third x) env)
                   (interp (fourth x) env)))
       (LAMBDA (let ((parms (second x))
                     (code (maybe-add 'begin (rest2 x))))
                 #'(lambda (&rest args)
                     (interp code (extend-env parms args env)))))
       (t      ;; a procedure application
               (apply (interp (first x) env)
                      (mapcar #'(lambda (v) (interp v env))
                              (rest x))))))))
这里是在添加了宏评估之后(为了清晰起见,参考链接中已经添加了子方法)

(defun interp (x &optional env)
  "Interpret (evaluate) the expression x in the environment env.
  This version handles macros."
  (cond
    ((symbolp x) (get-var x env))
    ((atom x) x)

    ((scheme-macro (first x))              
     (interp (scheme-macro-expand x) env)) 

    ((case (first x)
       (QUOTE  (second x))
       (BEGIN  (last1 (mapcar #'(lambda (y) (interp y env))
                              (rest x))))
       (SET!   (set-var! (second x) (interp (third x) env) env))
       (IF     (if (interp (second x) env)
                   (interp (third x) env)
                   (interp (fourth x) env)))
       (LAMBDA (let ((parms (second x))
                     (code (maybe-add 'begin (rest2 x))))
                 #'(lambda (&rest args)
                     (interp code (extend-env parms args env)))))
       (t      ;; a procedure application
               (apply (interp (first x) env)
                      (mapcar #'(lambda (v) (interp v env))
                              (rest x))))))))

有趣的是,的开头一章有一个非常类似的函数,他称之为
eval

谢谢你的回答,我可以将你的回答总结如下:for
defun
:1)evaluate AST(返回javascript
函数
对象)2)执行javascripts函数3)将结果值作为参数传递给lisp函数。这是我已经在做的。对于
defmacro
:1)idem 2)跳过3)将javascript函数作为参数传递给宏。`返回的结果应该是AST元素,应该对其进行计算和执行。这就留下了一个悬而未决的问题:
quote
应该返回什么?它应该是AST元素或javascript函数和其他OBJ的列表吗?(引号…)返回一个“stuff”列表,其中“stuff”的形式可能会在以后进行计算。lisp的美妙之处在于它的列表表示法与AST表示法相同,因此返回列表或AST是等效的。对于我来说,宏不计算其参数似乎是其性质的副作用,而不是其主要定义属性。感谢您的回复。如果我理解正确,macroexpand函数会将宏及其参数转换为无宏的lisp代码。对吗?你的描述有点不准确,但我认为你的想法是对的好的,我已经发出了精神上的咔哒声。我正在按如下方式重构我的实现:1/读取用户代码,转换为包含AstSymbol、AstKeyword、AstString、AstInteger的列表。。。当然还有其他名单;2/将库宏(
defmacro
)转换为AST;3/遍历用户AST以查找宏(
defmacro
);4/实际的宏扩展阶段:用宏的AST元素替换调用宏的AST元素(此处检测宏调用)(用相关的用户AST元素替换宏参数);5/AST现在应该是无宏的,解释。我也觉得很有帮助。链接断了。你能在别的地方找到吗?