4

clojure 分析库中,有一个原子存储一些分析信息,它被许多函数修改。在评论中,他们断言汇总统计信息(函数之一)是出于效率目的最后调用的,但我看不出这是如何在实际代码中执行的。

然后我注意到可以强制执行此操作的一种方式是构建地图的顺序,因此我重新创建了一个更简单的示例来演示。

(defn t []
  (let [a (atom {})]
    {:b (swap! a #(assoc % :data 'is-here))
     :c (keys @a)}))

在这里,与注释一致,代码中 :b 或 :c 中的哪一个将决定 a 的值。当然必须有一些排序,但是这种行为是否得到保证?似乎不应该这样,因为无序散列没有排序,这可能对并行性产生不利影响。

4

2 回答 2

3

我会认为地图文字的评估顺序是一个实现细节,并且在地图文字中使用具有副作用的非常量表达式(例如设置原子状态)是自找麻烦。

对于定义明确的评估语义,请使用函数调用,其中保证从左到右评估参数。

(let [i (atom 0)] 
  (hash-map :a (swap! i inc) :b (swap! i inc) :c (swap! i inc)))
;=> {:a 1, :c 3, :b 2}

请注意,由于我们使用了哈希映射,因此键是乱序的,但值与写入顺序中的键相对应。

实施细节

地图字面量(1.5 版)的实现细节取决于几件事:

  • 读者将未评估的映射文字视为小型映射(8 对或更少)的有序数组映射或较大映射的无序哈希映射。

  • 如果键和值是常量表达式,编译器评估器将读取器提供的映射表达式解析为无序哈希映射,而不管大小如何,但会将其评估为数组映射。否则,编译器-评估器将根据阅读器提供的密钥顺序进行评估,具体取决于大小。

一个小例子会让这更清楚一点(也许):

user=> {:a 1, :b 2, :c 3}
{:a 1, :c 3, :b 2}
user=> (type *1)
clojure.lang.PersistentArrayMap

user=> (def m1 {:a 1, :b 2, :c 3})
#'user/m1
user=> m1
{:a 1, :c 3, :b 2}
user=> (type m1)
clojure.lang.PersistentHashMap
user=> (eval m1)
{:a 1, :c 3, :b 2}
user=> (type *1)
clojure.lang.PersistentArrayMap

但...

user=> (def m2 {:a ((fn [] 1)), :b 2, :c 3})
#'user/m2
user=> m2
{:a 1, :b 2, :c 3}
user=> (type m2)
clojure.lang.PersistentArrayMap
于 2013-08-27T14:27:11.607 回答
2

我相信,只要{...}表单保持等于或小于 8 个映射整体(键/值对),就会保持顺序。A{...}将变为PersistentArrayMap具有 8 个或更少条目的 a,PersistentHashMap否则为 a。前者将保留其映射条目的给定顺序,保证不扩展到后者。来源

至于并行性,我认为以下简单测试表明,对于使用 8 个或更少条目创建的映射,映射条目将按照给定的顺序进行评估:

user=> (let [x (atom 0)]
         {:a (do (Thread/sleep 1000) (swap! x inc))
          :b (swap! x inc)})
{:a 1, :b 2}

但可能会以其他顺序评估使用超过 8 个条目创建的地图:

user=> (let [x (atom 0)]
         {:a (do (Thread/sleep 1000) (swap! x inc))
          :b (swap! x inc)
          :c (swap! x inc)
          :d (swap! x inc)
          :e (swap! x inc)
          :f (swap! x inc)
          :g (swap! x inc)
          :h (swap! x inc)
          :i (swap! x inc)})
{:a 1, :c 2, :b 3, :f 4, :g 5, :d 6, :e 7, :i 8, :h 9}

虽然,至少对于 9 个条目的哈希映射,在它们的实例化过程中似乎没有任何并行性。那很有意思。

于 2013-08-27T04:21:15.137 回答