有人可以使用(assoc-in)
来解释以下结果背后的原因吗?
(assoc-in {:foo {:bar {:baz "hello"}}} [:foo :bar] "world")
=> {:foo {:bar "world"}}
(assoc-in {:foo {:bar nil}} [:foo :bar :baz] "world")
=> {:foo {:bar {:baz "world"}}}
(assoc-in {:foo {:bar "hello"}} [:foo :bar :baz] "world")
=> ClassCastException java.lang.String cannot be cast to clojure.lang.Associative clojure.lang.RT.assoc (RT.java:702)
显然我可以用另一种数据类型(例如字符串)替换地图甚至nil
,但我不能用地图替换数据类型(例如String),因为它需要的数据类型已经是地图。
如何解决这个问题呢?我想实现以下目标:
(assoc-in {:foo {:bar "hello"}} [:foo :bar :baz] "world")
=> {:foo {:bar {:baz "world"}}}
答案 0 :(得分:6)
assoc-in
在assoc
之上实施。您可以替换地图和nil
,因为assoc
适用于它们:
(assoc {} :foo :bar) ;=> {:foo :bar}
(assoc nil :foo :bar) ;=> {:foo :bar}
但assoc
不适用于字符串:
(assoc "string" :foo :bar) ;=> ClassCastException
另外,assoc-in
的{{3}}非常优雅:
(defn assoc-in
;; metadata elided
[m [k & ks] v]
(if ks
(assoc m k (assoc-in (get m k) ks v))
(assoc m k v)))
如果您需要替换无法调用assoc
的值,则需要在较浅的某个级别上操作并替换整个地图而不仅仅是值:
(assoc-in {:foo {:bar "hello"}} [:foo :bar] {:baz "world"})
;=> {:foo {:bar {:baz "world"}}}
如果地图中还有其他值,您不想通过替换整个内容而丢失,则可以the definition使用assoc
:
(update-in {:foo {:bar "hello"}} [:foo] assoc :baz "hi")
;=> {:foo {:bar "hello", :baz "hi"}}
答案 1 :(得分:1)
推理:问题是你的:bar指向字符串“hello”而不指向地图。 随着你的工作可以使用七巧板的想法
答案 2 :(得分:1)
根据@jbm的回答,我查看了源代码并找到了以下解决方案(包括重新实现(assoc-in)
:
(defn assoc-in' [m [k & ks] v]
(if ks
(let [v' (get m k)
v'' (when (map? v') v')]
(assoc m k (assoc-in' v'' ks v)))
(assoc m k v)))
如果有人能够验证这个解决方案,我会很高兴。
答案 3 :(得分:0)
此代码可以解决这个问题,但我不知道它是否足以满足您的要求
(assoc-in {:foo {:bar "hello"}} [:foo :bar] {:baz "world"})
=> {:foo {:bar {:baz "world"}}}