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。

"在进行代码结构操作时,通常需要有一个保持键排序的映射。ArrayMap 就是这样的一种映射 -"  https://clojure.org/reference/data_structures#ArrayMaps
此外,我认为如果数组映射在语言中出现,那一定是出于某种原因,如果它不保留顺序,那它只是普通映射的低效版本(并且它确实保留了顺序;问题在于walker没有)。
这不仅仅是前序遍历,还有N个其他以映射为输入并返回映射的函数,不保证返回数组映射,因此不保留顺序,例如:conj(如果你有超过8个键),merge,assoc等。那些承诺不返回相同顺序的数组映射(而且在当前实现中确实不返回)的列表非常非常长。
你引用了官方Clojure.org关于数组映射的文档,其中也提到:注意,数组映射只会在'未修改'的情况下维护排序顺序。后续的关联操作最终会导致它'成为'一个哈希映射。
困惑的因子是实施中一般性的数组映射自动提升。用户定义的数组映射在关联操作中也会自动提升。默认情况下,数组映射(可能通过普通的哈希映射读取器字面量构造,或提供8个键值对的哈希映射构造器供给),如果它们的容 Training Sample量变为>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 的补丁正是我想要的。

数组映射似乎不是很有用或被定义。 但有序映射在某些罕见情况下是一种有用的抽象;也许它应该被添加到语言中。 与数组映射不同,它们将在伪修改下保留其属性。
以下是顺序映射(和集合)的实现示例

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

此外,CLJ-1239也是相关内容,它提出了一种基于协议的clojure.walk(我相信你可以通过扩展其Walkable协议来数组映射来获得所需的行为)

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