Clojure 如何实现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))) 现在我想添加宏--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函数,但这就是我遇到的问题。我想
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
谢谢你的回答,我可以将你的回答总结如下:fordefun
: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现在应该是无宏的,解释。我也觉得很有帮助。链接断了。你能在别的地方找到吗?