【发布时间】:2017-03-20 16:07:59
【问题描述】:
我对 Clojure 还是很陌生,而且我认为一般来说是编程,但我最终尝试编写某种 DSL 来生成 PDF 文件(使用 PDFBox,一个 java 库)。 我希望最终的语法看起来像这样:
(document {:name "test" :bleed "22mm" ...}
(page {:height "560mm" ...) (text {:color "red"} "blabla") (line-break))
所以问题是每个表达式都是一个宏,它会在 eval 时做一些事情 + 在第一个参数之后递归地读取每个表达式,并使用 conj'ed 选项作为它的第一个参数来扩展它。现在,例如,它看起来像这样:
(defmacro document
[{:keys [name] :as options}
& body]
`(let [~'pdoc ~'(PDDocument.)]
(do
~@(unfold (conj options {:pdoc 'pdoc}) ~body)
(doto ~'pdoc
(...)))
和展开宏(一个扩展参数):
(defmacro unfold
[options & body]
(loop [
[f & fs] body
opts options
output []]
(if (empty? f)
output
(recur fs
(conj opts (second f))
(conj output `(~(first f) ~(conj opts (second f)))))
)))
问题是我无法让它们一起工作。经过一些调试,我仍然得到java.lang.NullPointerException:。
我感觉问题出在unfold 输出到document 的方式上,但我完全无法理解宏在嵌套时的反应。当我使用macroexpand 时,嵌套宏永远不会扩展我不知道这是预期的还是我的编程错误。无论如何,当
(macroexpand '(unfold {...} (document) (document)))
我明白了
[(document {...}) (document {...})]
在我目前的理解中,这似乎没问题,因为我后来使用它unquote-splice'd。但我显然错过了一些东西。
--------- 编辑 ------------
对于一个很没用的
(document {:name "hey"}} (document {:name "ho"}))
我想以((doto ...) 为例)结束:
(let [pdoc (.PDDocument)]
(do
(let [pdoc (.PDDocument)]
(do
(doto pdoc .save (str "ho" ".pdf"))))
(doto pdoc .save (str "hey" ".pdf"))))
很抱歉,这篇非常具体且冗长的帖子,但我没有描述所有内容就无法解释问题。
提前谢谢你, 再见
【问题讨论】:
-
您能否在宏的示例调用中添加所需的输出?
-
您像调用函数一样调用
unfold,但它是宏。要么发出一个unfold(这需要发出一个do才能工作),要么使unfold成为一个函数。另外,请务必添加整个异常消息。 -
@ClojureMostly Hum 我想我明白了。首选的方法是什么?使它成为一个功能似乎更干净。这就是整个异常信息。奇怪的空……
-
我都看过了,所以这是你的偏好。如果您需要在代码中使用
unfold,则将其保留为宏。如果unfold仅用于库生成代码,那么一定要让它成为一个函数。所以仅供参考:一个宏有两个隐式参数,当像函数一样调用它时你不会传入。因此(可能)错误。另外,您似乎不希望unfold成为可变参数?