在Clojure中,如何解构一个map的所有键?

11
在Clojure中,可以像这样解构一个映射的一些键:
(let [{:keys [cpp js]} {:cpp 88 :js 90}] 
   (println js); 90
   (println cpp); 88
 )
有没有一种方法可以解构地图的所有键?
也许像这样:
(let [{:all-the-keys} {:cpp 88 :js 90}] 
   (println js); 90
   (println cpp); 88
 )
3个回答

25

实际上不行,而且这也不是个好主意。想象一下:

(let [{:all-the-keys} m]
  (foo bar))

foo和bar是全局变量吗?还是局部变量?你应该从m中提取哪些键?如果m“有时”包含foo键,而foo也是全局函数,那么这段代码应该怎么做?有时候你调用全局函数,有时候你调用存储在m中的函数?

忽略技术问题(可以克服),这真的是可读性和可预测性的灾难。明确地说明你想要提取哪些键;如果你经常想要提取相同的十个键,你可以编写一个简单的宏,如(with-person p body),以简化常见情况。


11

这个问题已经很老了,所以您可能已经忘记了它,但当我试图做同样的事情时,在谷歌上找到了它,所以如果我发布我的解决方案,它可能会帮助其他人。

(defmacro let-map [vars & forms]
  `(eval (list 'let (->> ~vars keys
                         (map (fn [sym#] [(-> sym# name symbol) (~vars sym#)]))
                         (apply concat) vec)
               '~(conj forms 'do))))

这基本上是将地图{:cpp 88 :js 90}转换为绑定形式[cpp 88 js 90],然后构造一个let绑定,同时执行一些eval-jitsu以确保这在运行时发生。

(def test-map {:cpp 88 :js 90})
(let-map test-map
  (println js)
  (println cpp))
;=> 90
;=> 88

1
噢,拜托,这是唯一一个实际回答标题问题的答案。谢谢。 - desudesudesu
5
这是一个非常聪明的宏。让我们都花点时间认识它的巧妙之处,然后承诺自己永远不会在生产代码中编写类似的东西。 - MichaelBlume
它可能看起来不是最佳实践,但我甚至会认为这种结构有有效的用例。 - Andreas Steffan
1
它并不真正起作用。(def test-map {:cpp (Object.)}) 报错 无法将对象嵌入代码中 - Andreas Steffan

7
您可以编写一个宏来实现这个功能(有效地创建一个小型DSL),但出于以下原因,我认为这不是一个很好的想法:
  • 为了在编译时创建正确的js和cpp字面量,您需要在编译时解构映射。这在您能够使用的方面上相当有限(例如,您必须事先指定键,并且不能在高阶函数中使用)
  • 当有更简单的方法可以完成工作时,宏通常是一个不好的想法(请参见下文)
在您的情况下,我建议只是使用一个简单的doseq来循环遍历映射。
(let [my-map {:cpp 88 :js 90}]
  (doseq [[k v] my-map]
    (println v)))

请注意:
  • 您可以像上面那样使用解构来提取每个映射条目的键k和值v
  • 我使用了doseq而不是for,因为它是非惰性的,并且在这个例子中似乎您仅将循环用于println的副作用。
  • 如果您希望得到一个惰性序列的值(88 90),那么for是合适的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接