【问题标题】:evaluate an arg when passing to a macro传递给宏时评估 arg
【发布时间】:2015-02-16 11:05:38
【问题描述】:

我有一个小宏,它将一系列 cmds 作为 clojure 的表达式并返回它们的字符串表示形式。我无法将参数传递给宏 -

(defmacro cmd [& cmds]
  (->> (for [v cmds]
         (join " " v))
       (join "; ")))

(defn install-jive [version]
  (cmd
   (yum remove jive-add-ons)
   (yum install jive-add-ons ~version)))

(install-jive 1.2)

我希望 fn 返回以下内容 -

(install-jive 1.2) => "yum remove jive-add-ons; yum install jive-add-ons 1.2"

但是目前它返回 -

"yum 删除 jive-add-ons; yum install jive-add-ons (clojure.core/unquote 版本)"

【问题讨论】:

  • 问题是宏在定义时被扩展(调用)。当 install-jive 被定义时,你有 ~version 而不是 1.2。
  • @cgrand 我有什么选择?如何构造宏以访问 var ?
  • 为什么是宏而不是函数?
  • @Shlomi 因为我希望能够编写 (yum install abc) - 在当前 ns.xml 中没有定义这些符号。有替代品吗?
  • @murtaza52 你必须使用转义机制来区分什么是静态的什么是动态的。您可以使用意外获得的取消引用表单作为转义机制。请参阅我对此类实施的回复。

标签: clojure


【解决方案1】:

您需要考虑需要宏生成哪些表单。例如,在您的情况下,您可能希望宏在扩展后生成类似这样的内容:

(defn install-jive [version]
  (clojure.string/join
    ";"
    [(clojure.string/join " " (list "yum" "remove" "jive-add-ons"))
     (clojure.string/join " " (list "yum" "install" "jive-add-ons" version))]))

宏将列表中的前 3 个元素转换为 String 并且没有触及 version。这是一个为一种形式执行此操作的宏:

(defmacro single-cmd [cmd-form]
  `(clojure.string/join " " (list ~@(map str (take 3 cmd-form))
                                  ~@(drop 3 cmd-form))))

扩展这个宏会生成这个:

(macroexpand '(single-cmd (yum remove jive-add-ons version)))
=> (clojure.string/join " " (clojure.core/list "yum" "remove" "jive-add-ons" version))

(macroexpand '(single-cmd (yum remove)))
=> (clojure.string/join " " (clojure.core/list "yum" "remove"))

这是我们每个命令扩展所需的内容。现在我们需要创建一个扩展它的宏:

(cmd
  (yum remove jive-add-ons)
  (yum install jive-add-ons version))

变成我们想要的形式。这是一个简单的版本,只是为了给你一个想法:

(defmacro cmd [& cmds]
  `(clojure.string/join
     ";"
     (list (single-cmd ~(first cmds))
           (single-cmd ~(second cmds)))))

扩展它生成:

(macroexpand '(cmd
                (yum remove jive-add-ons)
                (yum install jive-add-ons version)))
=> (clojure.string/join 
     ";" 
     (clojure.core/list (user/single-cmd (yum remove jive-add-ons)) 
                        (user/single-cmd (yum install jive-add-ons version))))

现在:

(install-jive 1.2)
=> "yum remove jive-add-ons;yum install jive-add-ons 1.2"

我的版本做了两个假设:前三个元素应始终转换为String,并且您始终使用两个且只有两个命令调用cmd。你可能需要改进它来解决你的问题。

但我认为这会让你朝着正确的方向前进。有一本很棒的书 Mastering Clojure Macros 可以帮助您发展宏技能。

【讨论】:

    【解决方案2】:
    (defmacro cmd [& cmds]
      `(join "; "
         [~@(for [cmd cmds]
             `(join " "
                [~@(for [arg cmd]
                     (if (and (seq? arg) (= (first arg) 'clojure.core/unquote))
                       (second arg)
                       (list 'quote arg)))]))]))
    

    如果您与原始代码进行比较,您会发现此代码将 join 调用推迟到运行时,并将取消引用的形式转换为生成的命令模板中的表达式。

    【讨论】:

      猜你喜欢
      • 2021-01-03
      • 1970-01-01
      • 1970-01-01
      • 2020-11-14
      • 1970-01-01
      • 1970-01-01
      • 2016-11-11
      • 2018-11-27
      • 1970-01-01
      相关资源
      最近更新 更多