Clojure:使用assoc-in的结果不一致

时间:2013-10-30 20:02:50

标签: clojure

有人可以使用(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"}}}

4 个答案:

答案 0 :(得分:6)

assoc-inassoc之上实施。您可以替换地图和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"}}}