【问题标题】:Convert function to another form inside macro将函数转换为宏内的另一种形式
【发布时间】:2012-01-29 18:12:29
【问题描述】:

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

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

我想使用宏来转换上述表格并在我的系统中处理它,但是我在尝试弄清楚如何让宏的link 部分正常工作时遇到了麻烦。我的链接功能是这样的

(defn link [top-section-id link-text]
    ....)

你可以看到这需要两个参数,但是我上面的定义只传递一个参数。我想要做的是“转换”通过DSL传入的数据,将上面top-section的id注入到链接函数中。

所以实际上它应该将输入转换为

(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"))

要取回列表形式,但还有其他方法吗?

【问题讨论】:

    标签: clojure


    【解决方案1】:

    您可以将top-section 扩展为特制的let 表单,将link 符号绑定到原始link 函数与top-section 表单的第一个参数的部分应用:

    (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):

    user> (top-section 1 "start of text"
            (link "more text")
            (link "still more"))
    "start of text"
    1 "more text"
    1 "still more"
    nil
    

    如果top-sections 可能需要嵌套,您可以使用更复杂的top-section,它会小心抓取“命名空间范围”link

    (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
    

    (接下来是一个很可能完全不必要的复杂化——top-section 的可配置变体——如果没有用,希望它有点令人愉快......)

    顺便说一句,您是否有一小部分固定的函数想要以这种方式处理,或者您认为它可能会扩展/变得很大?在后一种情况下,您可以让 top-section 对所有持有的符号执行相同的操作,例如在某处的 Atom 中:

    (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> (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!ing 在新符号中的操作可以通过一个简单的函数/宏 (register-top-section-symbol?) 来美化。

    【讨论】:

    • 哇,太好了,谢谢,这正是我想要的!
    【解决方案2】:

    如果top-section 是一个宏,它会不计算link 形式,因此它可以以任何它想要的方式转换它们。

    不过,我会提出一些不同的建议:让宏 top-section 在某个动态变量绑定到 top-section 的相应参数的上下文中评估其子形式,并从 link 中引用它功能:

    (def ^:dynamic *id*)
    
    (defmacro top-section [id text & body]
      `(binding [*id* ,id]
         ...
         ~@body))
    
    (defun link [text]
      ... *id* ...)
    

    【讨论】:

    • 您认为在这种情况下使用动态变量是最佳选择吗?然而,我是 Clojure 菜鸟,这似乎有点不必要的附带复杂性。
    • 回应您的第一句话 - 真的是这样吗?我不断收到 arity 错误,因为我认为它正在查找 link 函数然后退出。在完成转换之前,我需要对这些函数进行评估
    • @e-i-s 恕我直言,使用动态变量比对代码进行魔术转换引入的复杂性更低,因为它不会将功能与确切的数据表示联系起来。考虑将link 调用包装在另一个函数中时会发生什么。动态变量仍然有效。代码转换将中断。将不必要的东西捆绑在一起就是偶然复杂性的定义,不是吗?
    • @djhworld 是的。你的宏对身体形式有什么作用?
    • @Matthias,说得好。但是,当您使用变量来解析链接调用中的顶部 id 时,这不会将您的链接函数实现与宏联系起来吗?另外,您如何看待在宏范围内捕获 id 符号而不是具有命名空间级别的 var?
    【解决方案3】:

    你可以试试这个:

    (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")))
    

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

    【讨论】:

    • 代码遍历通常是递归的。在唯一有意义的地方使用递归并没有错。 :)
    猜你喜欢
    • 2015-05-11
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    • 2020-11-21
    • 1970-01-01
    • 2021-05-13
    相关资源
    最近更新 更多