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

Kre*_*tur 4 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)
Run Code Online (Sandbox Code Playgroud)

显然我可以替换地图甚至nil替换另一种数据类型(例如String),但我无法用地图替换数据类型(例如String),因为它需要该数据类型已经是地图.

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

(assoc-in {:foo {:bar "hello"}} [:foo :bar :baz] "world")
=> {:foo {:bar {:baz "world"}}}
Run Code Online (Sandbox Code Playgroud)

jbm*_*jbm 6

assoc-in是在...之上实现的assoc.您可以替换地图,nil因为assoc它们适用于它们:

(assoc {} :foo :bar)  ;=> {:foo :bar}
(assoc nil :foo :bar) ;=> {:foo :bar}
Run Code Online (Sandbox Code Playgroud)

assoc不适用于字符串:

(assoc "string" :foo :bar) ;=> ClassCastException
Run Code Online (Sandbox Code Playgroud)

顺便说一句,定义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)))
Run Code Online (Sandbox Code Playgroud)

如果需要替换那个值 assoc无法调用你需要在一个较浅的层次上操作并替换整个地图而不仅仅是值:

(assoc-in {:foo {:bar "hello"}} [:foo :bar] {:baz "world"})
;=> {:foo {:bar {:baz "world"}}}
Run Code Online (Sandbox Code Playgroud)

如果有,你不希望通过更换整个事情失去了地图中的其他值,可以使用update-in具有assoc:

(update-in {:foo {:bar "hello"}} [:foo] assoc :baz "hi")
;=> {:foo {:bar "hello", :baz "hi"}}
Run Code Online (Sandbox Code Playgroud)