混淆的因素是实现中普遍的array map自动提升。甚至用户定义的array map在assoc操作时也会自动提升。默认情况下,array-maps(可能通过正常的hash-map阅读器文字或hash-map构造函数提供的8个键值创建),如果其大小变为> 8,将自动提升为hash maps。即使是用户定义的array map也不是免疫的;如果您保持计数不变,您可以在保留array-map实现的情况下更新现有键,但只要增加一个新条目,结构就会提升为hashmap(以提高效率)。
为了公平起见,使用identity进行遍历,我预计输入将保持不变,并且不会返回hashmap(这是我的假设)。按照当前实现,walk实际更改了array map,尽管实际上并没有对其进行修改。
这就是`walk`实现中的情况,它会在`cond`的条件中下陷,直到遇到通用的集合实现。
(coll? form) (outer (into (empty form) (map inner form)))
`empty`创建一个空的array map,然后通过`conj`(实际上是暂时的通过`conj!`)扩展,最终达到array map的8个限制并提升到hashmap。
一种简单的解决方案是提供一个定制的用于“walk”的案例,它可以检测数组映射并将其保持原样。要实现这一点,您必须显式地构建数组映射。
(defn walk
[inner outer form]
(cond
(list? form) (outer (apply list (map inner form)))
(instance? clojure.lang.IMapEntry form)
(outer (clojure.lang.MapEntry/create (inner (key form)) (inner (val form))))
(seq? form) (outer (doall (map inner form)))
(instance? clojure.lang.PersistentArrayMap form)
(outer (apply array-map (reduce (fn [acc [k v]] (conj acc (inner k) (inner v))) [] form)))
(instance? clojure.lang.IRecord form)
(outer (reduce (fn [r x] (conj r (inner x))) form form))
(coll? form) (outer (into (empty form) (map inner form)))
:else (outer form)))
如果您对核心的walk做了猴子补丁,它似乎可以工作
clojure.walk=> (def x (array-map :a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9 :j 10))
#'clojure.walk/x
clojure.walk=> (def xw (clojure.walk/postwalk identity x))
#'clojure.walk/xw
clojure.walk=> xw
{:a 1, :b 2, :c 3, :d 4, :e 5, :f 6, :g 7, :h 8, :i 9, :j 10}
clojure.walk=> (= xw x)
true
clojure.walk=> (= (seq xw) (seq x))
true
clojure.walk=> (type xw)
clojure.lang.PersistentArrayMap
然而,升级仍然可能,例如,如果`outer`对生成的映射做了某种增长操作。