将数据结构传递给宏以进行填写

时间:2014-08-30 06:04:48

标签: macros clojure

我正在尝试解决问题:我需要从传入的值创建一个映射,但是当值的符号名称一致时,它们映射到的不是。例如:我可能会传递一个用户ID的值。在代码中,我始终可以使用符号user-id - 但根据其他因素,我可能需要制作地图{"userId" user-id}{"user_id" user-id}{:user-id user-id}或 - 好吧,你明白了。

我可以写一个让我分道扬的宏:

(defmacro user1 [user-id] `{"userId" ~user-id}
(defmacro user2 [user-id] `{"user_id" ~user-id}

但我宁愿做的是定义一组地图,然后将它们与一组给定的符号组合:

(def user-id-map-1 `{"userId" `user-id}
(defn combiner [m user-id] m)            ;; <-- Around here, a miracle occurs.

我无法弄清楚如何进行此评估。似乎我能够制作包含未评估符号的地图,然后在函数或宏的词法范围内查找这些符号,将这些符号绑定为本地符号 - 但如何

3 个答案:

答案 0 :(得分:2)

使用带有标准关键字密钥的地图,而不是标准化您的符号名称。你不需要靠近宏,你可以把你的地图变成记录,如果需要没有太多麻烦。

你所知道的

(def user1 {:id 3124, :surname "Adabolo", :forenames ["Julia" "Frances"]})

...可以通过使用您选择的任何函数映射键来进行转换:

(defn map-keys [keymap m]
  (zipmap (map keymap (keys m)) (vals m)))

例如,

(map-keys name user1)
;{"id" 3124, "surname" "Adabolo", "forenames" ["Julia" "Frances"]}

(map-keys {:id :user-id, :surname :family-name} user1)
;{:user-id 3124, :family-name "Adabolo", nil ["Julia" "Frances"]}

如果要删除nil条目,请将表达式包装在(dissoc ... nil)中:

(defn map-keys [keymap m]
  (dissoc
    (zipmap (map keymap (keys m)) (vals m))
    nil))

然后

(map-keys {:id :user-id, :surname :family-name} user1)
;{:user-id 3124, :family-name "Adabolo"}

我从具有优先权的Michał Marczyk's answer看到,上面基本上重写了clojure.set/rename-keys,但是...... {/ p>

  • 没有破坏钥匙:

例如,

(clojure.set/rename-keys user1 {:id :user-id, :surname :family-name})
;{:user-id 3124, :forenames ["Julia" "Frances"], :family-name "Adabolo"}
  • 不适用于正常功能:

例如,

(clojure.set/rename-keys user1 name)
;IllegalArgumentException Don't know how to create ISeq from: clojure.core$name ... 

如果您放弃使用falsenil作为键,则可以保持丢失的键不变,仍然使用普通功能:

(defn map-keys [keymap m]
  (zipmap (map #(or (keymap %) %) (keys m)) (vals m)))

然后

(map-keys {:id :user-id, :surname :family-name} user1)
;{:user-id 3124, :family-name "Adabolo", :forenames ["Julia" "Frances"]}

答案 1 :(得分:1)

如何将传入的值放入由正式参数名称伪造的关键字键入的地图中:

(defmacro zipfn [map-name arglist & body]
  `(fn ~arglist
     (let [~map-name (zipmap ~(mapv keyword arglist) ~arglist)]
       ~@body)))

使用示例:

((zipfn argmap [x y z]
   argmap)
 1 2 3)
;= {:z 3, :y 2, :x 1}

更好的是,不要使用宏:

;; could take varargs for ks (though it would then need another name)
(defn curried-zipmap [ks]
  #(zipmap ks %))

((curried-zipmap [:x :y :z]) [1 2 3])
;= {:z 3, :y 2, :x 1}

然后您可以使用clojure.set/rename-keys重新输入此地图:

(clojure.set/rename-keys {:z 3, :y 2, :x 1} {:z "z" :y "y" :x "x"})
;= {"x" 1, "z" 3, "y" 2}

这里的第二张地图是&#34;翻译地图&#34;为了钥匙;你可以通过合并像{:x "x"}这样的地图来构建,描述如何重命名各个键。

答案 2 :(得分:0)

对于您描述的问题,我找不到使用宏的原因。

我推荐像

这样的东西
(defn assoc-user-id 
  [m user-id other-factors]
  (assoc m (key-for other-factors) user-id))

您实施key-for的位置,以便根据other-factors选择密钥。