2024 Clojure状态调查中分享您的想法!

欢迎!请参阅关于页面以获取更多关于如何使用本网站的信息。

0
集合

在我看来,walker没有正确处理array-maps,它将它们转换为普通映射并丢失了排序信息。

> (def x (array-map :a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9 :j 10))
#'x
> (class x)
clojure.lang.PersistentArrayMap
> x
{:e 5, :g 7, :c 3, :j 10, :h 8, :b 2, :d 4, :f 6, :i 9, :a 1}
> (def xw (clojure.walk/postwalk identity x))
#'xw
> (class xw)
clojure.lang.PersistentHashMap
> xw
{:e 5, :g 7, :c 3, :j 10, :h 8, :b 2, :d 4, :f 6, :i 9, :a 1}

1 答案

0

映射是无序的,并且通常映射“修改”操作不保证排序。因此,我认为这不是一个bug。

"在进行代码形式转换时,往往需要保持键顺序不变的映射。数组映射就是这样一种映射-"  https://clojure.org/reference/data_structures#ArrayMaps
此外,我认为如果数组映射存在于语言中,那么它肯定有存在的理由,而如果它不保留顺序,那它就仅仅是一个普通映射的低效版本(而且它确实保留了顺序;这是因为遍历器不保留顺序)。
不仅仅是后序遍历,还有许多函数接受映射作为输入并返回映射,它们都不保证返回数组映射,因此不保留顺序,例如: conj(如果你超过8个键)、merge、assoc等。那些不保证返回相同顺序的数组映射的列表非常非常长(并且当前实现中确实是这样)。
您引用了 Clojure.org 的官方文档中对数组映射的描述,其中也提到您引用的同一段落:"请注意,只有当数组映射不被修改时,才会维护排序顺序。随后的 assoc-ing 将最终导致它'变成'哈希映射。”
混杂因素在于数组映射在实现中的自动提升。即使是用户定义的数组映射在关联时也会自动提升。默认情况下,数组映射(可能是通过常规的哈希映射读取器字面量构造,或提供8个键值的哈希映射构造函数),如果大小改变大于8,则会自动提升为哈希映射。即使是用户定义的数组映射也无法免疫;如果您保持计数一致,则可以在保留数组映射实现的同时更新现有键,但一旦添加新条目,结构就会提升为哈希映射(以提高效率)。

公平地说,按照标识符进行遍历,我本以为输入将保持不变,并且不会返回哈希映射(这是我的假设)。但实际上,即使没有真正对其进行修改,walk函数也会更改数组映射

这是`walk`中的实现中发生的事情,它会穿过`cond`的条件,直到遇到通配符集合实现

    (coll? form) (outer (into (empty form) (map inner form)))

`empty`创建一个空的数组映射,然后通过`conj`(实际上是通过`conj!`临时)进行扩展,最终在8个数组映射的限制上冲过,变成哈希映射。

一个简单的解决方案是为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)))

如果您对core.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`对生成的映射进行某些操作以扩展它。
by
感谢所有评论!对walker的补丁正是我想做的。

Array-map似乎并不非常有用或按定义使用。但有序映射在某些罕见情况下是一种有用的抽象;也许应该将其添加到语言中。与array-maps不同,它们在伪变异下会保留其本质。
by
以下是有序映射(和集合)的实现

https://github.com/clj-commons/ordered

此外,CLJ-1239也有所关联,它提议基于协议的clojure.walk(我相信你可以通过将Walkable协议扩展到array maps来获得你期望的行为)

https://clojure.atlassian.net/browse/CLJ-1239
...