【问题标题】:Pass a data structure in to a macro for filling in将数据结构传递给宏以进行填充
【发布时间】:2014-08-30 06:04:47
【问题描述】:

我正在尝试解决一个问题:我需要从传入的值创建一个映射,但是虽然这些值的符号名称是一致的,但它们映射到的 却不是。例如:我可能会收到一个用户 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.

我不知道如何进行此评估。似乎我应该能够制作一个包含未评估符号的映射,然后在将这些符号绑定为局部变量的函数或宏的词法范围内查找这些符号——但是 怎么样

【问题讨论】:

  • 现在有两个很好的答案;我希望我可以将两者都标记为正确。谢谢你们,SO的聪明人。

标签: macros clojure


【解决方案1】:

不要标准化您的符号名称,而是使用带有标准关键字键的映射。您不需要靠近宏,如果需要,您可以将地图转换为记录,而不会有太多麻烦。

你知道的

(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,但是...

  • 保持丢失的键不变:

例如,

(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"]}

【讨论】:

  • 啊,很聪明!你为我节省了很多时间来处理丢失的钥匙——非常好。
【解决方案2】:

如何将传入的值放在由形式参数名称伪造的关键字作为键的映射中:

(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}

这里的第二个映射是键的“翻译映射”;您可以通过合并映射来构建,例如 {:x "x"},描述应该如何重命名各个键。

【讨论】:

    【解决方案3】:

    对于你描述的问题,我找不到使用宏的理由。

    我会推荐类似的东西

    (defn assoc-user-id 
      [m user-id other-factors]
      (assoc m (key-for other-factors) user-id))
    

    您在哪里实现key-for,以便它根据other-factors 选择密钥。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-10
      • 2021-12-29
      • 1970-01-01
      • 2014-09-17
      • 2019-04-14
      • 2017-11-17
      • 1970-01-01
      • 2016-04-16
      相关资源
      最近更新 更多