在clojure profiling library中,有一个存储一些分析信息的原子,它由许多函数修改。在评论中,他们声称为了提高效率,最后调用摘要统计信息(其中一个函数),但我无法看到它是如何在实际代码中强制执行的。
然后我注意到这可以强制执行的一种方式是构造地图的顺序,所以我重新创建了一个更简单的例子来演示。
(defn t []
(let [a (atom {})]
{:b (swap! a #(assoc % :data 'is-here))
:c (keys @a)}))
在这里,与评论一致,代码中的哪一个:b或:c首先将确定a的值。当然必须有一些排序,但这种行为是否得到保证?看起来它不应该是,因为无序散列没有排序,这可能对并行性产生不利影响。
答案 0 :(得分: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}
请注意,因为我们使用了hash-map,所以键是乱序的,但是这些值对应于所写顺序中的键。
实施细节
地图文字的实现细节(版本1.5)取决于几个方面:
读者将未评估的地图文字视为小地图(8对或更少)的有序数组映射或较大地图的无序散列映射。
< / LI>如果键和值是常量表达式,编译器评估程序将读取器提供的映射表达式解析为无序散列映射,而不考虑大小,但会将其作为数组映射进行评估。否则,编译器评估程序将根据读者提供的密钥顺序进行评估。
一个小例子可以让它更清晰一些(或许):
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
答案 1 :(得分:2)
我相信,只要{...}
形式保持等于或小于8个地图条目(键/值对),订单就会保持不变。 {...}
将成为包含8个或更少条目的PersistentArrayMap
,否则将成为PersistentHashMap
。前者将保留其映射条目的给定顺序,保证不扩展到后者。 Source
至于并行性,我认为以下简单测试表明,对于使用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个条目的哈希映射,似乎并不是在他们的瞬间进行任何并行性。这很有趣。