【问题标题】:Clojure Maps, order of key value creationClojure Maps,键值创建的顺序
【发布时间】:2013-08-27 02:45:43
【问题描述】:

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

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

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

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

【问题讨论】:

  • 我发现了别的东西,原来它取决于键的字母顺序,大概是它们以某种方式排序的方式。 IE。如果在 aboce 示例中键是 {:d (swap!...) :a (keys ...)},则行为不同。
  • 其实不仅如此,行为很奇怪,我不会依赖它。
  • 您指的是 pdata-stats 吗? pdata-stats 的所有参数都不是可变的,因此在该函数中生成映射键的顺序无关紧要。
  • 我指的是 pdata-stats,尤其是在“with-pdata”宏中。顺序是相关的,':results (do ~@body)' 有副作用,必须在调用 pdata-stats 之前对其进行评估,否则 'stats' 将是一个空原子。
  • 这与 pdata-stats 无关,然后 pdata-stats 获取传入的任何内容并且不取消引用。问题是它的 arg 是首先被取消引用,还是它配置的函数首先在 with-pdata 中被评估。是的,它依赖于评估顺序是一种糟糕的风格(如果不是逻辑错误的话)。

标签: map clojure parallel-processing


【解决方案1】:

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

对于定义明确的求值语义,使用函数调用,保证参数从左到右求值。

(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,因此键是乱序的,但值与写入顺序中的键相对应。

实现细节

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

  • 读者将 unevaluate 映射文字视为小映射(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

【讨论】:

    【解决方案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 个条目的哈希映射,在它们的实例化过程中似乎没有任何并行性。这很有趣。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-19
      • 2020-03-10
      • 2010-11-10
      • 2019-08-24
      • 1970-01-01
      相关资源
      最近更新 更多