如果元素使用相同的形式生成,则无法使用字面量语法创建具有唯一成员/键的集合/字典。此类合法形式的示例:(rand)、(read)、(clojure.core.async/<!!)、等等。我将在这些示例中使用(rand)。
user=> #{(rand) (rand)}
IllegalArgumentException 重复键:(rand) clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:68)
user=> {(rand) 1, (rand) 2}
IllegalArgumentException 重复键:(rand) clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
看起来输入在评估集合构造函数的参数之前就进行了重复检查。然而,这并不能防止稍后再次进行检查。
请注意,即使重复元素没有作为字面量出现在源代码中,重复检查也仍然是(正确地)在评估后被发现的。
user=> #{(+ 1 1) 2}
IllegalArgumentException 重复键:2 clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:56)
user=> {(+ 1 1) :a, 2 :b}
IllegalArgumentException 重复键:2 clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
因此,第一次重复检查似乎既冗余又错误。
请注意,这种急切的重复检查似乎比语法引号读取宏优先。
user=> `#{~(rand) ~(rand)}
IllegalArgumentException 重复键:(clojure.core/unquote (rand)) clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:68)
user=> `{~(rand) 1, ~(rand) 2}
IllegalArgumentException 重复键:(clojure.core/unquote (rand)) clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
这很奇怪——因为语法引号在读取时根本不会实现一个集合
bq. 对于列表/向量/集合/字典,语法引号建立了一个对应数据结构的模板。在模板内部,未命名的形式作为递归语法引号处理,但可以通过使用未命名或未命名拼接来免除这种递归引号,在这种情况下,它们将被当作表达式处理,并在模板中用它们的值或值的序列替换。 (
https://clojure.org/reader)
抛开定义不谈,根据语法引号显然的扩展,我期望前面的情况能正确工作。
通过手动替换所需的输入来模拟预期的宏展开,我得到了预期结果
user=> '`#{~:a ~:b}
(clojure.core/apply clojure.core/hash-set (clojure.core/seq (clojure.core/concat (clojure.core/list :b) (clojure.core/list :a))))
user=> (clojure.core/apply clojure.core/hash-set (clojure.core/seq (clojure.core/concat (clojure.core/list (rand)) (clojure.core/list (rand)))))
#{0.27341896385866227 0.3051522362827035}
user=> '`{~:a 1, ~:b 2}
(clojure.core/apply clojure.core/hash-map (clojure.core/seq (clojure.core/concat (clojure.core/list :a) (clojure.core/list 1) (clojure.core/list :b) (clojure.core/list 2))))
用户=> (clojure.core/apply clojure.core/hash-map (clojure.core/seq (clojure.core/concat (clojure.core/list (rand)) (clojure.core/list 1) (clojure.core/list (rand)) (clojure.core/list 2))))
{0.12476921225204185 2, 0.5807961046096718 1}
在我看来,在设置/映射的读取宏评估它们的参数之前,似乎在进行不必要的重复检查。这个检查应该是可以移除的。即使这个检查没有捕捉到一些错误的重复情况(实际上它做到了),但是因为明显的第二次评估检查会捕捉到所有的真实重复情况,所以它是不必要的。
话虽如此,这个检查是否应该发生本身就不太清楚。如果我尝试创建包含重复成员/键的集合/映射,我将不会得到错误。重复项会无声地被移除或替换。
用户=> (set (list 1 1))
#{1}
用户=> (hash-map 1 2 1 3)
{1 3}
对于用读取语法构造的常数来说,似乎这样做最为一致。
我可以看出,字面表达式的表示并不是“构造请求”,而更像是一种尝试复制字面数据对象的打印表示。从这个角度来看,禁止“非法”的打印表示似乎是有道理的。不幸的是,由于列表在读取时就会进行评估,所以字面向量、集合和映射中的评估形式的存在(已经打破了这种理论)。也就是说,这种集合的打印表示不是一个可准确读的形式,所以读取时的重复检查仍然不能防止打印/读取表示中的似乎是矛盾的一致性。
用户=> '#{(+ 1 1) 2}
#{(+ 1 1) 2}
user=> #{(+ 1 1) 2}
IllegalArgumentException 重复键:2 clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:56)
鉴于这个问题无法完全避免,将读取器字面量构造器与他们运行时对应的处理方式处理得最简单、最一致,就像语法引号在没有虚假的重复检查的情况下做的那样。