Clojure: Inconsistent results using assoc-in

天大地大妈咪最大 提交于 2019-12-23 14:03:22

问题


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


来源:https://stackoverflow.com/questions/19692764/clojure-inconsistent-results-using-assoc-in

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!