【问题标题】:Clojure macro: Create local vars from a map [duplicate]Clojure 宏:从地图创建本地变量 [重复]
【发布时间】:2023-04-09 12:00:02
【问题描述】:

我有这个示例代码,我在其中通过迭代映射的键值对来创建变量。

(defmacro block [bindings & body] 
  `(let [
    ~@(mapcat (fn [[k v]] [(if (symbol? k) k (symbol (name k))) `~v]) bindings) ]
  ~body))

(block {:a 1 :b 2} (if true (prn "true" a b) (prn "false")))

它工作正常。 输出:“真”1 2


但现在我想传递与 var 相同的映射,但它会抛出异常。

IllegalArgumentException 不知道如何从:clojure.lang.Symbol 创建 ISeq

(def ctx {:a 3 :b 4})

(block ctx (if true (prn "true" a b) (prn "false")))

【问题讨论】:

    标签: clojure


    【解决方案1】:

    当您传递一个引用包含映射的 var 的符号时,它不起作用的原因是,宏只看到符号字面量,而不是它所引用的值。当您向它传递地图文字时,宏会看到地图文字。

    如果您希望它也支持符号,则需要 resolve 符号所指的 var 并获取其值,例如(var-get (resolve bindings)):

    (defmacro block [bindings & body]
      `(let [~@(mapcat (fn [[k v]] [(if (symbol? k)
                                      k
                                      (symbol (name k))) `~v])
                       (cond
                         (map? bindings) bindings
                         (symbol? bindings) (var-get (resolve bindings))
                         :else (throw (Exception. "bindings must be map or symbol"))))]
         ~@body))
    
    (block ctx (if true (prn "true" a b) (prn "false")))
    "true" 3 4
    (block {:a 3 :b 4} (if true (prn "true" a b) (prn "false")))
    "true" 3 4
    

    您还需要使用~@body“拼接”到表单中,因为body 将是一个表单序列,即使它只包含一个表单。

    在排除故障时查看宏的扩展方式也有帮助:

    (macroexpand-1 '(block ctx (if true (prn "true" a b) (prn "false"))))
    => (clojure.core/let [a 3 b 4] (if true (prn "true" a b) (prn "false")))
    

    【讨论】:

    • 当你想在 func 中使用它时会发生什么
    • @JAtkin 宏在函数中的行为与在其他任何地方的行为相同——它们在调用它们的代码被评估之前被“扩展”。
    • 当我传递一个变量时,这会为我抛出一个 NullPointerException。
    猜你喜欢
    • 2013-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多