请在 2024 年 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅关于 页面以获取更多有关如何操作的详细信息。

+3
Collections

大家好!我们最近发现,在将 update-vals 应用到排序后的映射时,由于收集无法转换为瞬时的,它会转换回未排序的数组/哈希映射。下面给出一个示例。值得注意的是,这与 medley/map-vals 不同,后者保留排序,而我们之前就是用这个功能替换 1.11。

这是预期的行为吗?如果是这样,我很好奇背后的意图 :) 谢谢!

user> (def m (sorted-map :a 0 :b 0))
;; => #'user/m
user> (type m)
;; => clojure.lang.PersistentTreeMap
user> (type (update-vals m inc))
;; => clojure.lang.PersistentArrayMap
user> (type (medley.core/map-vals inc m))
;; => clojure.lang.PersistentTreeMap

2 个答案

+3

被选择
 
最佳答案

是的,这是预期的行为。在通用映射类型和性能方面存在权衡。考虑了两种选择,最终决定优先考虑最常见的类型(哈希和数组映射)的性能。

如文档字符串所述,此函数返回“一个新的映射”,不会保留映射类型。

刚刚遇到了这个胡说八道的问题。考虑到许多其他函数也会保持类型不变,文档中也没有相反的说明,因此保持类型是一种逻辑预期。函数的文档字符串称该函数返回“一个新的映射”并不能表明类型已经改变。我们处理的是不可变的数据结构,显然函数返回一个新的映射,否则很难做到。
0

这种设计是有意为之的,是为了性能。排序映射主要用于调试

如果您想使排序映射工作,请使用此回答底部的代码

```clojure
(defn update-vals
  "m f => {k (f v) ...}

  Given a map m and a function f of 1-argument, returns a new map where the keys of m
  are mapped to result of applying f to the corresponding values of m."
  {:added "1.11"}
  [m f]
  (with-meta
    (persistent!
      (reduce-kv (fn [acc k v] (assoc! acc k (f v)))
        (if (instance? clojure.lang.IEditableCollection m)
          (transient m)
          (transient (empty m)))
        m))
    (meta m)))

(update-vals
  (sorted-map
    :d :e
    :b :c
    :h :a
    :a :g
    )
  str)
```

错误

```clojure
1. Unhandled java.lang.ClassCastException
   class clojure.lang.PersistentTreeMap cannot be cast to class clojure.lang.IEditableCollection
   (clojure.lang.PersistentTreeMap and clojure.lang.IEditableCollection are in unnamed module of loader 'app')
```

与排序映射一起工作的代码,但速度较慢

```clojure
(defn update-vals
  "m f => {k (f v) ...}

  Given a map m and a function f of 1-argument, returns a new map where the keys of m
  are mapped to result of applying f to the corresponding values of m."
  {:added "1.11"}
  [m f]
  (with-meta
    (reduce-kv (fn [acc k v] (assoc acc k (f v)))
      (empty m)
      m)
    (meta m)))
```
谢谢,但我看不出为什么`update-vals`不能在使用排序映射的同时保持对未排序映射的性能,因此我怀疑这个函数没有支持排序映射的意图,因为这样的文档说明我没有看到:)
我发布的代码经过编辑以在排序映射上工作,这就是`(transient (empty m))`所做的事情。代码抛出了错误。我猜`assoc!`在排序映射上无法工作。

`(assoc! (sorted-map :a :b) :c :d)` 产生了一个错误
"assoc!" 适用于瞬态。请参阅 API 文档:https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/assoc!
在第一个例子中我使用了瞬态。但第二个例子中我出错。
=>(assoc! (transient (sorted-map :a :b)) :c :d)
   对于对象:{:a :b},没有定义 IEditableCollection.-as-transient 协议方法。
我不理解这个点
>> 排序列表主要用于调试
对我来说,排序列表似乎是一个完美的数据结构。不确定调试在这里有何作用。
...