可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Can someone explain what's the reasoning behind the following results using (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)
Apparently I can replace a map and even nil
with another data type (e.g. String) but I can not replace a data type (e.g. String) with a map, because it need that data type to be already a map.
And how would one work around this? I would like to achieve the following:
(assoc-in {:foo {:bar "hello"}} [:foo :bar :baz] "world") => {:foo {:bar {:baz "world"}}}
回答1:
assoc-in
is implemented on top of assoc
. You can replace maps and nil
because assoc
works on them:
(assoc {} :foo :bar) ;=> {:foo :bar} (assoc nil :foo :bar) ;=> {:foo :bar}
But assoc
doesn't work on strings:
(assoc "string" :foo :bar) ;=> ClassCastException
As an aside, the definition of assoc-in
is quite graceful:
(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)))
If you need to replace a value that assoc
can't be called on you need to instead act on one level shallower and replace the whole map rather than just the value:
(assoc-in {:foo {:bar "hello"}} [:foo :bar] {:baz "world"}) ;=> {:foo {:bar {:baz "world"}}}
If there are other values within the map that you don't want to lose by replacing the whole thing, you can use update-in
with assoc
:
(update-in {:foo {:bar "hello"}} [:foo] assoc :baz "hi") ;=> {:foo {:bar "hello", :baz "hi"}}
回答2:
Reasoning: The problem is that your :bar is pointing to string "hello" not to a map. As work around you can use tangrammer's idea
回答3:
Based on the answer by @jbm I looked at the source and came to the following solution (including reimplementing (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)))
I would be glad if someone could verify this solution.
回答4:
This code does the work around but I don't know if it will be enough for your requirements
(assoc-in {:foo {:bar "hello"}} [:foo :bar] {:baz "world"}) => {:foo {:bar {:baz "world"}}}