Clojure 将函数转换为宏中的另一种形式

Clojure 将函数转换为宏中的另一种形式,clojure,Clojure,我正在写一个有点像这样的小数据文件 (top-section 1 "start of text" (link "bit of text") (link "bit of text 2")) 我想用一个宏来转换上面的表单并在我的系统中处理它,但是我在尝试如何使宏的链接部分正常工作时遇到了困难。我的链接函数是这样的 (defn link [top-section-id link-text] ....) 正如您所看到的,这需要两个参数,但是我上面的定义只传递一个参数。我想做的是“转换

我正在写一个有点像这样的小数据文件

(top-section 1 "start of text"
  (link "bit of text")
  (link "bit of text 2"))
我想用一个宏来转换上面的表单并在我的系统中处理它,但是我在尝试如何使宏的
链接
部分正常工作时遇到了困难。我的链接函数是这样的

(defn link [top-section-id link-text]
    ....)
正如您所看到的,这需要两个参数,但是我上面的定义只传递一个参数。我想做的是“转换”通过DSL传入的数据,将上面的
顶部部分的id注入到link函数中

因此,在现实中,它应该将输入转换为

(top-section 1 "start of text"
  (link 1 "bit of text")
  (link 1 "bit of text 2"))
如果Clojure阅读器不评估代码并抛出一个错误,说我只向
link
函数传递了一个参数,我怎么能做到这一点呢。是否存在“转义”输入的方法,以便在我进行必要的转换之前不会对其求值

我知道我能做到

(top-section 1 "start of text"
  '(link "bit of text")
  '(link "bit of text 2"))

要返回列表表单,还有其他方法吗?

如果
顶部部分
是一个宏,它将获取未计算的
链接
表单,以便可以以任何方式转换它们

不过,我建议做一些不同的事情:让宏
top section
在某个动态变量绑定到
top section
的相应参数的上下文中评估其子窗体,并从
链接
函数中引用:

(def ^:dynamic *id*)

(defmacro top-section [id text & body]
  `(binding [*id* ,id]
     ...
     ~@body))

(defun link [text]
  ... *id* ...)
你可以试试这个:

(defmacro transforming [& body]
  `(do ~@(map (fn xform [[f arg1 arg2 & more :as syms]]
                (if (= f 'top-section)
                  (apply list f arg1 arg2
                    (map #(if (= (first %) 'link)
                            (apply list (first %) arg1 (rest %))
                            (xform %))
                    more))
                  syms))
              body)))
然后像这样使用它:

(transforming
  (top-section 1 "start of text"
    (link "bit of text")
    (link "bit of text 2")
    (top-section 3 "nested"
      (link "nested sections should work too")))
  (top-section 2 "section two"
    (link "text")
    (link "text 2")))
这将扩展到:

(do
  (top-section 1 "start of text"
    (link 1 "bit of text")
    (link 1 "bit of text 2")
    (top-section 3 "nested"
      (link 3 "nested sections should work too")))
  (top-section 2 "section two"
    (link 2 "text")
    (link 2 "text 2")))

但是,这个宏有一个递归调用,我很确定它可以变得更加漂亮。

您可以将
顶部部分扩展为一个精心编制的
表单将
链接
符号绑定到原始
链接
函数的部分应用程序,并将其绑定到
顶部部分
表格:

(defmacro top-section [n s & forms]
  `(let [~'link (partial ~'link ~n)]
     (prn ~s) ; handle s in whichever way is appropriate
     ~@forms))

;; for the sake of example
(defn link [n s] (prn n s))
REPL交互(打印三行,
nil
返回):


如果
顶部部分
s可能需要嵌套,则可以使用更复杂的
顶部部分
,该部分会注意获取“名称空间范围”
链接

(defmacro top-section [n s & forms]
  (let [qlink (symbol (name (.. (resolve 'link) ns name)) "link")]
    `(let [~'link (partial ~qlink ~n)]
       (prn ~s)
       ~@forms)))
在REPL上:

user> (top-section 1 "start of text"
        (link "more text")
        (link "still more")
        (top-section 2 "inner section"
          (link "etc.")))
"start of text"
1 "more text"
1 "still more"
"inner section"
2 "etc."
nil
user> (swap! top-section-syms conj 'prn)
#{prn link}
user> (top-section 1 "start of text"
        (link "more text")
        (link "still more")
        (top-section 2 "inner section"
          (link "etc.")
          (prn "and another fn...")))
"start of text"
1 "more text"
1 "still more"
"inner section"
2 "etc."
2 "and another fn..."
nil

(接下来可能是一个完全不必要的复杂问题——
顶部部分的一个可配置变量——
——如果没有用处,希望它会有点令人愉快……)

顺便问一下,您是否有一个小的、固定的函数集希望以这种方式处理,或者您认为它可能会扩展/变成大的?在后一种情况下,您可以让
顶部部分
对所有符号执行相同的操作,例如在某个原子中:

(def top-section-syms (atom #{'link}))

(defmacro top-section [n s & forms]
  (let [nsym (gensym "n")
        qs (for [s @top-section-syms]
             [s (symbol (name (.. (resolve s) ns name)) (name s))])]
    `(let [~nsym ~n
           ~@(->> (for [[s q] qs]
                    [s `(partial ~q ~nsym)])
                  (apply concat))]
       (prn ~s)
       ~@forms)))
在REPL上:

user> (top-section 1 "start of text"
        (link "more text")
        (link "still more")
        (top-section 2 "inner section"
          (link "etc.")))
"start of text"
1 "more text"
1 "still more"
"inner section"
2 "etc."
nil
user> (swap! top-section-syms conj 'prn)
#{prn link}
user> (top-section 1 "start of text"
        (link "more text")
        (link "still more")
        (top-section 2 "inner section"
          (link "etc.")
          (prn "and another fn...")))
"start of text"
1 "more text"
1 "still more"
"inner section"
2 "etc."
2 "and another fn..."
nil

swap的操作register top section symbol
?)来修饰。

您认为在这种情况下使用动态变量是最好的选择吗?然而,我是Clojure noob,这似乎有点不必要的偶然性。作为对你第一句话的回应——真的是这样吗?我不断得到算术错误,因为我假设它正在查找
链接
函数,然后退出。我需要这些函数在我完成计算之前不被评估transformations@e-i-s IMHO,使用动态变量比在代码上进行魔术转换带来的复杂性更低,因为它不会将功能与精确的数据表示联系起来。考虑当你在另一个函数中包装<代码>链接< /代码>调用时会发生什么。动态变量仍将工作。代码转换将中断。把不需要的东西绑在一起是偶然复杂性的定义,不是吗?@djhworld是的,是的。你的宏对身体形态有什么作用?@Matthias,说得好。但是,当您使用变量解析链接调用中的顶部节id时,这难道不会将链接函数实现与宏联系起来吗?另外,您认为在宏范围中捕获id符号而不是使用名称空间级别的变量怎么样?代码遍历通常是递归的。如果递归是唯一有意义的东西,那么使用递归没有什么错。:)哇,很好的回答,谢谢,这正是我想要的!