4

有人可以解释以下结果背后的原因是什么(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另一种数据类型(例如字符串),但我不能用地图替换数据类型(例如字符串),因为它需要该数据类型已经是地图。

以及如何解决这个问题?我想实现以下目标:

(assoc-in {:foo {:bar "hello"}} [:foo :bar :baz] "world")
=> {:foo {:bar {:baz "world"}}}
4

4 回答 4

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非常优雅:

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

如果地图中有其他值您不想通过替换整个内容而丢失,则可以使用update-inwith assoc

(update-in {:foo {:bar "hello"}} [:foo] assoc :baz "hi")
;=> {:foo {:bar "hello", :baz "hi"}}
于 2013-10-30T22:01:21.300 回答
1

推理:问题在于您的 :bar 指向字符串“hello”而不是地图。作为解决方法,您可以使用 tangrammer 的想法

于 2013-10-30T21:51:14.153 回答
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)))

如果有人可以验证此解决方案,我会很高兴。

于 2013-10-31T08:40:08.630 回答
0

此代码可以解决问题,但我不知道它是否足以满足您的要求

(assoc-in {:foo {:bar "hello"}} [:foo :bar] {:baz "world"})
=> {:foo {:bar {:baz "world"}}}
于 2013-10-30T20:34:15.893 回答