1

我试图完全理解编译时宏的局限性。

这是一个宏(我完全知道这不是最佳实践宏):

(defmacro emit (language file &body body)
  (print language)
  (print file)
  (print body)
  (with-open-file (str file :direction :output :if-exists :supersede)
    (princ (cond ((eq language 'html)
                  (cl-who:with-html-output-to-string (s nil :prologue t :indent t) body))
                 ((eq language 'javascript)
                  (parenscript:ps body))
                 ((eq language 'json)
                  (remove #\; (parenscript:ps body))))
           str)))

我编译宏:

; processing (DEFMACRO EMIT ...)
PROGRAM> 

我编译这个表格:

PROGRAM> (compile nil (lambda () (emit json "~/file" (ps:create "hi" "hello") (ps:create "yo" "howdy"))))

JSON 
"~/file" 
((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy")) 
#<FUNCTION (LAMBDA ()) {5367482B}>
NIL
NIL
PROGRAM> 

编译时print输出是我所期望的。

但是,如果我看~/file

body

似乎((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy"))从未替换过参数body,因此从未处理过。

为什么是这样?

以及关于这个主题的最佳文学作品是什么?

4

2 回答 2

1

parenscript:ps是一个宏,而不是一个函数:它的主体是文字 parenscript,不被评估而是编译,从 Parenscript 到 JavaSctipt。这很容易检查:

> (parenscript:ps body)
"body;"

我对您应该阅读的内容没有任何建议:这个宏看起来非常混乱,我无法真正理解潜在的意图是什么。CL 中的宏是一个函数,其参数是某种语言 L1 的源代码,并返回某种语言 L2 的源代码,其中 L2 通常是 L1 的子集。但是,如果这只是某人在需要函数时认为需要宏的正常情况,或者是否是其他一些混乱,我无法解决。

于 2022-02-25T13:33:18.697 回答
1

为什么要替代?你从来没有替换过任何东西。

宏定义了一个宏替换函数,该函数应用于代码中的实际形式以生成另一个形式,然后编译该形式。当您将宏定义应用于这些参数时,它会在宏扩展时做各种事情(写入文件等),然后返回返回的内容princ,这正是它的第一个参数,然后编译这个返回的形式。我不认为那是你想要的。

似乎您实际上想要做的是扩展为一种以多种方式中的一种解释正文的形式,如第一个参数所示。

您需要做的是返回新表单,以便

(emit 'html "foo.html"
  (:html (:head) (:body "whatever")))

扩展到

(with-open-file (str "foo.html" :direction :output :etc :etc)
  (cl-who:with-html-output (str)
    (:html (:head) (:body "whatever")))

为此,我们有一个模板语法:反引号。

`(foo ,bar baz)

意思是一样的

(list 'foo bar 'baz)

但使转换后的代码结构更加清晰。还有,@把东西拼接成一个列表。

`(foo ,@bar)

意思是一样的

(list* 'foo bar)

即 的内容bar,当它们是列表时,被拼接到列表中。这对于宏中的主体特别有用。

(defmacro emit (language file &body body)
  `(with-open-file (str ,file :direction :output :if-exists :supersede)
     (princ (cond ((eq ,language 'html)
                   (cl-who:with-html-output-to-string (s nil :prologue t :indent t)
                     ,@body))
                  ((eq ,language 'javascript)
                   (parenscript:ps ,@body))
                  ((eq ,language 'json)
                   (remove #\; (parenscript:ps ,@body))))
            str)))

请注意我在哪里引入了反引号来创建模板和逗号以将外部参数放入其中。另请注意,参数是forms

这有几个问题:宏的用户无法知道硬编码的符号。在一种情况下(str)他们必须注意不要隐藏它,在另一种情况下( )他们s必须知道它才能写信给它。为此,我们使用生成的符号(forstr以便不会发生冲突)或让用户说出他们想要命名的符号(for s)。此外,这cond可以简化为case

(defmacro emit (language file var &body body)
  (let ((str (gensym "str")))
    `(with-open-file (,str ,file
                      :direction :output
                      :if-exists :supersede)
       (princ (case ,language
                ('html
                 (cl-who:with-html-output-to-string (,var nil
                                                     :prologue t
                                                     :indent t)
                   ,@body))
                ('javascript
                 (parenscript:ps ,@body))
                ('json
                 (remove #\; (parenscript:ps ,@body))))
              ,str)))

但是,您可能希望在宏扩展时已经确定输出代码。

(defmacro emit (language file var &body body)
  (let ((str (gensym "str")))
    `(with-open-file (,str ,file
                      :direction :output
                      :if-exists :supersede)
       (princ ,(case language
                 ('html
                  `(cl-who:with-html-output-to-string (,var nil
                                                       :prologue t
                                                       :indent t)
                     ,@body))
                 ('javascript
                  `(parenscript:ps ,@body))
                 ('json
                  `(remove #\; (parenscript:ps ,@body))))
              ,str)))

在这里,您可以看到case表单已经在宏扩展时进行了评估,然后使用内部模板来创建内部表单。

这一切都完全未经测试,因此删除小错误作为练习^^。

Paul Graham 的《On Lisp》是一本对宏写作有很多话要说的书。Peter Seibel 的免费 »Practical Common Lisp« 也有一个章节,Edi Weitz 的 »Common Lisp 食谱« 中也有一些食谱。

于 2022-02-25T16:43:55.643 回答