在Clojure中嵌套地图中关联多个键/值的惯用方法是什么?

byt*_*lub 30 idiomatic clojure

想象一下你有这样的地图:

(def person {
  :name {
    :first-name "John"
    :middle-name "Michael"
    :last-name "Smith" }})
Run Code Online (Sandbox Code Playgroud)

更改与两者相关的值的惯用方法是什么:first-name和:last-name在一个表达式中?

(澄清:假设您想要设置:名字为"Bob"和:姓氏为"Doe".我们也说这个地图中还有其他一些我们要保留的值,所以从头开始构建它不是一个选项)

Bri*_*per 44

这有几种方法.

user> (update-in person [:name] assoc :first-name "Bob" :last-name "Doe")
{:name {:middle-name "Michael", :last-name "Doe", :first-name "Bob"}}

user> (update-in person [:name] merge {:first-name "Bob" :last-name "Doe"})
{:name {:middle-name "Michael", :last-name "Doe", :first-name "Bob"}}

user> (update-in person [:name] into {:first-name "Bob" :last-name "Doe"})
{:name {:middle-name "Michael", :last-name "Doe", :first-name "Bob"}}

user> (-> person 
          (assoc-in [:name :first-name] "Bob")
          (assoc-in [:name :last-name]  "Doe"))
{:name {:middle-name "Michael", :last-name "Doe", :first-name "Bob"}}
Run Code Online (Sandbox Code Playgroud)

编辑

update-inassoc在地图上做递归 在这种情况下,它大致相当于:

user> (assoc person :name 
             (assoc (:name person) 
                    :first-name "Bob" 
                    :last-name "Doe"))
Run Code Online (Sandbox Code Playgroud)

随着您深入了解一系列嵌套地图,重复键变得越来越乏味. update-in递归可以避免:name一遍又一遍地重复键(例如); 中间结果存储在递归调用之间的堆栈上.看一下update-in的来源,看看它是如何完成的.

user> (def foo {:bar {:baz {:quux 123}}})
#'user/foo

user> (assoc foo :bar 
             (assoc (:bar foo) :baz 
                    (assoc (:baz (:bar foo)) :quux 
                           (inc (:quux (:baz (:bar foo)))))))
{:bar {:baz {:quux 124}}}

user> (update-in foo [:bar :baz :quux] inc)
{:bar {:baz {:quux 124}}}
Run Code Online (Sandbox Code Playgroud)

assoc是动态的(如同update-in,assoc-in和上Clojure的数据结构的操作大部分其他的Clojure函数).如果assoc在地图上,则返回地图.如果你assoc在矢量上,它会返回一个矢量.查看assoc的源代码并查看RT.javaClojure源代码中的详细信息.