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
另外,我认为如果数组映射在语言中存在,那么它肯定有存在的理由,如果它不能保持顺序,那它只是常规映射的低效版本(而且它确实能保留顺序;顺序是由遍历器不保留的)。
这不仅仅是在后遍历中,还有其他N个以映射作为输入并返回映射的函数,并保证不会返回数组映射,因此不会保留顺序,例如:并发(如果你有超过8个键),合并,关联等。保证返回具有相同顺序的数组映射(而且在当前实现中确实不保证)的列表非常非常长。
你引用了Clojure.org官方文档中关于数组映射的说明,其中在同一段落你也引用了:“请注意,数组映射只有在未进行修改时才会保持排序顺序。后续的关联操作最终会导致它变成哈希映射。”
混乱的因素在于实现中数组映射的一般自动提升。用户定义的数组映射在关联操作上也会自动提升。默认情况下,数组映射(可能是通过常规哈希映射读取器文本或带有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)))

如果您修改了核心的“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的补丁正是我所想的。

根据定义,数组映射似乎不是很实用或常用。但有序映射在极少数情况下是一个有用的抽象;也许应该将其添加到语言中。与数组映射不同,它们在假性修改下会保留其本质。
作者:
以下是有序映射(和集合)的实现

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

此外,CLJ-1239 也与此相关,该提案提出了一种基于协议的 clojure.walk (我认为您可以通过扩展其 Walkable 协议到数组映射来获取所需的行为)

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